Style assembleur RISC-V

Sommaire

Remarques générales

Même s’il est lisible par des humains (du moins plus lisible que du code machine binaire), l’assembleur n’est pas habituellement destiné à être un langage source, mais plutôt un langage intermédiaire durant la compilation de logiciels.

Dans le cadre du cours INF2171 Organisation des ordinateurs, nous concevons directement des programmes en assembleur, possiblement conséquents. Cela nous encourage à mettre en place des règles de styles afin de faciliter le développement, la révision et la correction de programmes.

Les choix stylistiques essayent de privilégier la lisibilité des programmes qui sont destinés à être relus et compris: par des étudiants, des professeurs ou des correcteurs. Toutefois, le programme doit fonctionner en conformité avec ses exigences (cahier des charges). Le style essaye également d’aider à écrire des programmes corrects.

Remarques sur la correction de travaux

Il existe trois niveaux d’exigences pour chaque règle de style présenté.

  • Si aucune indication n’est donnée, la règle doit être suivie sous pénalité de perte de points.
  • Si la règle commence avec “Tentez de”, la règle est possiblement non applicable dans certains cas. Il est de votre responsabilité de justifier votre décition de ne pas suivre la rgle. Un manque répété non judicieux et non justifié entrainera une perte de points.
  • Si la règle commence avec “Pensez à”, la règle indique une convention commune qui peut être remplacée si vous exprimez une bonne raison personnelle en gardant la consistance et lisibilité en tête.

Pour facilier la comprhénsion de la correction, les fautes de style sont groupés en 5 catégories : SOURCE, COMMENTAIRE, LISIBILITE, STRUCTURE, INSTRUCTION. En regle générale, un erreur danns la catégorie SOURCE entraine une pénalité de 10%. Dans les quatre autres catégories, une erreur entraine généralement une pénalité de 5%.

Chaque catégorie pouvant entrainer une pénalité jusqu’à 20% (donc une pénalité maximale de 100% pour un TP qui n’a vraiement aucun style).

Fichier source (#SOURCE)

  • Nos programmes sont petits et tiennent tous dans un fichier.
  • Utilisez une extension .s.
  • Respectez le nom du fichier demandé.
  • Livrez le fichier tel quel (pas d’archive).

Le programme commence par un entête (plusieurs lignes de commentaire) qui indique

  • Le titre ou une description coute du programme.
  • Les identifiants des auteurs:
    • prénoms et noms,
    • codes permanents (4 majuscules, 8 chiffres, pas d’espaces. Vérifiez deux fois que le code est bon),
    • groupes cours,
    • vérifiez une 3e fois le code permanent.
  • Une description de la conception générale et des choix.
  • Tentez de décrire les comportements laissés explicitement ou implicitement libres dans le sujet du TP (en cas de doute sur le sujet du TP, demandez des éclaircissements).
  • Pensez à décrire les limites du programme: cas non gérés, tests échoués, limites connues, etc.
  • Même si le nombre de lignes n’est pas une métrique à optimiser, pensez à de viser une solution simple et généraliste plutôt qu’énumérer et tester l’ensemble des possibilités.

Bon:

# TP1 - Optimisation de matrices génomiques
# Jean Privat PRIJ12345678 (groupe 256)
# Alan Doe ADOE00000000 (groupe pi)
#
# Ce programme optimise les matrices génomiques jusqu'à une dimension de 42.
# Il y a un bogue avec la cytosine ce qui fait malheureusement 
# échouer le 31e test (voir commentaire à l'étiquette cytosine:).

Commentaires (#COMMENTAIRE)

  • Utilisez des commentaires pour expliquer le but et la logique des différents morceaux de code.
  • Commentez en anglais ou en français, mais soyez cohérent et vérifiez l’orthographe.
  • Tentez d’être concis et précis.
  • N’utilisez pas les commentaires pour traduire les instructions assembleur.
  • Ne commentez pas chaque instruction assembleur.
  • Un commentaire sur la même ligne qu’une instruction ou une directive commente celle-ci.
  • Un commentaire seul sur une ou plusieurs lignes commente les instructions ou directives qui suivent.
  • Tentez de commenter les parties complexes. Pensez à découper les parties complexes en morceaux plus simples.
  • Commentez et justifiez les astuces et les morceaux de code moins évidents. Pensez à éviter les astuces. S’il faut dix lignes de commentaires pour expliquer et justifier une astuce qui fait gagner deux lignes d’assembleur, l’astuce ne vaut peut-être pas le coup.

Bon:

        .data
score:  .word 0         # Score global à maximiser
        .eqv largeur, 8 # Largeur de la matrice génomique en nombre d'éléments

        .text
# Affichage de la matrice. L'affichage se fait ligne par ligne
affichage:
        li s0, 0        # Index de la ligne courante

Pas bon:

        .data           # Début des data
score:  .word 0
        .eqv largeur, 8 # Score à 0 et Largeur=8

        # On commence le début du texte
        .text
affichage:              # Afichage (étiket)
        li s0, 0
        # Mettrez 0 dans le registre CPU s0 [pseudo-instruction de type immédiates]

Correction du pas bon :

        # COMMENTAIRE! Ne commentez pas chaque instruction assembleur.
        .data           # Début des data
score:  .word 0
        # COMMENTAIRE! - Un commentaire à la fin d'un ligne doit commentez la ligne seulement.
        .eqv largeur, 8 # Score à 0 et Largeur=8
        

        # COMMENTAIRE! # Ne commentez pas chaque instruction assembleur.
        # On commence le début du texte
        .text
        # COMMENTAIRE! # Orthographe
affichage:              # Afichage (étiket)
        li s0, 0
        # COMMENTAIRE! # Ne pas faire la traduction de l'assembleur
        # Mettrez 0 dans le registre CPU s0 [pseudo-instruction de type immédiates]

Indentation et espace (#LISIBILITE)

  • Tentez de ne pas dépasser 80 caractères par ligne.
  • 100 caractères par ligne grand maximum.
  • Alignez les étiquettes sur la première colonne (pas d’indentation).
  • Pensez à utiliser un caractère tabulation ou 8 espaces pour indenter. Dans tous les cas, vous devez être consistants.
  • Alignez les instructions et directives sur la même colonne (un niveau d’indentation).
  • Si une étiquette est trop grande, mettez les données ou instructions à la ligne suivante.
  • Tentez d’aligner les commentaires d’instruction et de directive.
  • Utilisez un espace entre les instructions (et directives) et leurs opérandes.
  • Séparez les opérandes avec des virgules (pas d’espace avant la virgule, un espace après).
  • Tentez de séparer les blocs d’instruction par des lignes vides.

Bon:

        .data
score:  .word 0         # Score global à maximiser
        .eqv largeur, 8 # Largeur de la matrice génomique en nombre d'éléments

        .text
# Affichage de la matrice. L'affichage se fait ligne par ligne
affichage:
        li s0, 0        # Index de la ligne courante

Pas bon:

.data
  score: .word 0 # Score global à maximiser



     .eqv largeur 8 # Largeur de la matrice génomique en nombre d'éléments que l'on compte à partir de 1. Par exemple: 8, 12 ou 27 (dans les cas où on préfère 27), lol
           .text

#       Affichage de la matrice. L'affichage se fait ligne par ligne
affichage: li          s0, 0 # Index de la ligne courante

Structuration du code (#LISIBILITE)

  • Tentez de diviser le code en blocs logiques qui sont lisibles indépendamment.
  • Tentez de documenter les blocs en expliquant (voire justifiant) leur rôle et leur mise en œuvre.
  • Pensez à éviter les morceaux de codes identiques. Pensez à factoriser, si ce n’est pas possible ou avantageux, documenter les différences pour aider le lecteur.
  • Pour les TP2 et TP3, utilisez des routines pour rendre le programme plus lisible et facile à gérer. Et plus facile pour vous pour déboguer.

Instructions (#INSTRUCTION)

  • L’ISA RISC-V visée est RV64IMD. Cochez bien le mode 64 bit de RARS.
  • Pour le TP1: il y a des restrictions sur l’utilisation des instructions, les instructions load et store sont interdits ainsi que la pseudoinstruction la.

Registres (#LISIBILITE)

  • Utilisez les noms d’ABI des registres, car ils donnent une indication de leurs rôles et de leurs contraintes. (pas de registre x12).
  • Tentez d’utiliser les registres s (sauvés) pour les registres de travail général.
  • Pour les TP2 et TP3, tentez d’utiliser les registres a (arguments) pour travailler dans le cas des routines sans appel de sous-routine et éviter ainsi de modifier les registres s.
  • Tentez de ne pas réutiliser un même registre s pour des informations différentes dans un même morceau de code.
  • Pour des registres très temporaires (résultat intermédiaire par exemple), pensez à utiliser les registres t.
  • Commentez le rôle des registres utilisés. Tentez de le faire à leur initialisation.

Bon:

        li s0, 50       # Nombre de tours de boucle restant à effectuer

Pas bon:

        li x10, 50      # Registre x10 initialisé à 50

Symboles et étiquettes (#STRUCTURE)

  • Mettez la section .data avant la section .text. Cela permet de déclarer des symboles avec des directives .eqv dans la section .data et de les rendre visibles dans .text.
  • Nommez les symboles et étiquettes en anglais ou en français, soyez cohérents.
  • Tentez d’utiliser des noms courts, mais significatifs. Évitez les abréviations obscures.
  • Pour les TP2 et TP3, préfixez (ou suffixez) les étiquettes d’une même routine avec élément commun (nom ou initiales de la routine par exemple).
  • Pensez à nommer les étiquettes en fonction du code qui suit (plutôt qu’en fonction du code qui branche dessus).
  • Pour les données chaînes de caractères (.string), pensez de suffixer ou préfixer avec str ou msg par exemple.
  • Tentez de ne pas utiliser des valeurs numériques arbitraires directement dans le code. Utilisez plutôt des symboles définis avec .eqv.

Branchements et sauts (#STRUCTURE)

  • Tentez d’utiliser les sauts inconditionnels (j) pour implémenter des structures de contrôles, et non pour brancher à gauche et à droite compliquant la lecture du code.
  • Tentez de préférer du code linéaire qui mimique les structures de contrôle classiques (if, do, for).
  • Tentez d’identifier ces structures de contrôle dans les commentaires
  • Pensez à garder un certain ordre dans les étiquettes: fin est mieux à la fin.

Bon:

        li s0, 10       # index de boucle i
loop:   blez s0, fin    # for(i=10; i>0; i--) {
        mv a0, s0
        call printInt   #   print(i);
        addi s0, s0, -1 #   // décrément de boucle
        j loop          # }
fin:    li a0, 0
        call exit       # exit(0);

Pas bon:

        li s0, 10
        j debut
debut:  j test
decremt:addi s0, s0, -1
test:   bgtz s0, loop
        j fin
fin:    li a0, 0
        call exit
loop:   mv a0, s0
        call printInt
        j decremt

Appels système et appels de routines (#STRUCTURE)

  • Vous pouvez utiliser les appels système RARS ou les fonctions de la bibliothèque libs.s, mais ne mélangez pas les deux.
  • Déclarez les appels systèmes utilisés avec .eqv au début de la section .text. Utilisez les noms standards RARS (qui commencent par une majuscule).
  • Pour les appels, tentez d’initialiser les arguments dans l’ordre.
  • Pour les appels système, tentez de commencer par initialiser a7.
  • Si un argument a déjà la bonne valeur, signalez-le par un commentaire qui montre qu’il n’a pas été oublié.

Bon

        # Appels système RARS utilisés
        .eqv PrintInt, 1
# ...
        # Affiche la réponse à la grande question sur la vie, l'Univers et le reste
        li a7, PrintInt
        li a0, 42
        ecall

Pas bon:

        li a0, '*'
        li a7, 1
        ecall      # eappel

Définitions de routines (TP2 et TP3)

  • Documentez le rôle et les règles d’utilisation des routines que vous définissez. (#COMMENTAIRE)
  • Utilisez l’ABI RISC-V classique: (#LISIBILITE)
    • a0 à a7 pour les arguments, a0 et a1 pour les valeurs de retour.
    • s0 à s11 doivent être sauvegardées par une routine qui les utilise.
    • a0 à a7 et t0 à t6 sont considérés perdu après un appel de routine (jal, call, jalr).
  • Documentez les paramètres d’entrée et de sortie, ainsi que toute autre information pertinente. (#COMMENTAIRE)
  • Tentez de limiter et documenter l’accès (lecture et/ou écriture) à des données globales en mémoire. (#COMMENTAIRE)
  • Chaque routine doit être autonome et indépendante. (#STRUCTURE)
  • Utilisez un seul prologue et un seul épilogue par routine. (#STRUCTURE)
  • Pensez à n’utiliser qu’un seul ret par routine. (#STRUCTURE)

Optimisation du code (#INSTRUCTION)

  • Pensez à écrire du code simple, clair et lisible en premier.
  • Si vous n’arrivez pas à mettre au point la conception de votre programme (boucles, conditions, routines, etc), essayez de le prototyper en Java.
  • Utilisez des pseudoinstructions plutot que des instructions primitives quand c’est pertinent.
  • Sauf contre-indication particulière, il n’est pas demandé de produire du code machine extrêmement optimisé pour la performance. Optimisez plutôt l’exactitude et la lisibilité.
  • Tentez d’éviter les redondances et complexités inutiles qui rendent le code difficile à lire et cache souvent des bogues.