CSC4536– Compilation : De l'algorithme à la porte logique

Portail informatique

Mémento architecture et assembleur MIPS

Adapté de : https://minnie.tuhs.org/CompArch/Resources/mips_quick_tutorial.html

Ce mémento décrit les bases de l'architecture MIPS et le langage assembleur accessible par exemple avec des simulateurs comme MARS ou SPIM. On se limite aux fonctionnalités directement utiles dans le cours, mais pour une information complète, on pourra consulter le menu "Help" intégré dans l'application MARS.

Types de données et Constantes littérales

Types de données


  • Les instructions sont toutes sur 4 octets
  • Les données sont accessibles sous 3 formes : byte(1 octet), halfword (2 octets), word (4 octets)
  • Un caractère utilise 1 octet de mémoire
  • Un entier utilise 4 octets de mémoire

Constantes littérales


  • Les nombres sont utilisés tel que, i.e. 42, ou aussi 0xbabe
  • Les caractères sont entourés par des simples quote, i.e. 'b'
  • Les chaînes de caractères sont entourées par des doubles quotes, i.e. "Une chaine"
  • Les noms de label sont liés au charset de l'environnement JAVA et peuvent donc être en utf8 ou autre, i.e. Матрёшка:

Structure d'un programme

Un fichier du langage assembleur MIPS est un fichier texte contenant des déclarations de données (data segment) et le code du programme (text segment). Les déclarations de données peuvent être intercalées dans le code du programme.

Commentaires et Indentations


Les commentaires peuvent apparaître dans toutes les parties d'un fichier MIPS.
Un commentaire commence par le caractère # et s'étend jusqu'à la fin de la ligne.
L'indentation est libre pour le confort de lecture.

Labels


Les labels sont utilisés par l'assembleur pour identifier des adresses mémoires statiques :
  • Un label est identifié par un nom suivi du caractère :
  • Dans les déclarations de données, les labels sont des adresses mémoire de variables
  • Dans le code du programme, les labels sont des adresses de saut dans le code
  • Dans tous les cas, les valeurs de ces adresses sont calculées automatiquement par le programme d'assemblage

Déclarations de données


Une section de déclaration déclare des noms de variable, initialise leur valeur, et réalise leur allocation mémoire dans la zone des données statiques.
  • La section est identifiée par la directive
    .data # éventuellement une adresse en argument
  • Le format d'une déclaration de variable est
    name: storage_type value(, value)*
  • Par l'exemple :
    .data # Déclarations de variables à suivre var1: .word 666 # int var1 = 666; var2: .half 42 # short var2 = 42; array1: .word 4, 8, 0x10 # int[3] array1 = {4, 8, 16} array2: .byte 42, 'Z' # char[2] array2 = {'*', 'Z'} array3: .space 20 # 20 octets non initialisés : byte[20]/int[5]/byte[4][5] str1: .asciiz "str" # chaîne ASCII terminée par \0 str2: .asciiz "a\nb" # chaîne multi-ligne ASCII terminée par \0 str3: .ascii "bon" # Chaîne ASCII non terminée par \0 .ascii "jour" # Chaîne ASCII suite .byte 0 # et une terminaison nulle
  • Avec .word et .half, les adresses seront automatiquement alignées sur des mots de 4 octets ou 2 octets. Dans tous les cas, on peut forcer l'alignement d'une variable sur une frontière de 2^n octets avec la directive .align n avant la déclaration de variable :
    .align 3 # aligner la prochaine adresse sur un mot de 2^3 octets array4: .space 42 # l'adresse de array4 est un multiple de 8

Code du programme


  • Le code du programme est identifiée par la directive
    .text
  • Le code contient des instructions et des labels pour les sauts.
  • Le point d'entrée pour l'exécution est donnée par le label main:
  • La terminaison de l'exécution est réalisée en fin de fichier ou utilise l'appel système exit (cf. plus loin)

Exemple de fichier MIPS


# Un canevas de programme assembleur MIPS .data # Des déclarations de variables à suivre hello: .asciiz "hi world!" # ma première variable MIPS .text # Des instructions du programme à suivre fonct: # label pour la fonction "fonct" # ... des instructions ... main: # Le point d'entrée du programme # ... des instructions ... .data # Des déclarations de variables à suivre num: .word 0

Les registres MIPS

  • 32 registres pour l'usage général (+ 32 registres pour les instructions flottantes)
  • Dans l'assembleur, les noms de registre commencent par $ avec deux formes au choix :
    • utilisant le numéro du registre, i.e. de $0 à $31
    • utilisant un nom conventionnel, i.e. $sp, $t1...
  • 2 registres spéciaux "Lo" et "Hi" sont utilisés pour les résultats de multiplications ou de divisions. Ils ne sont accessibles qu'avec les instructions spéciales mfhi (Move From Hi) et mflo (Move From Lo)
  • La pile à la "tête en bas" : l'adresse de sommet de pile décroit quand la pile grandit
Numéro Nom Sous-titre Description
0 zero zero toujours la valeur 0
1 at assembler
temporary
réservé par l'assembleur
pour les pseudo-instructions
2-3 v0-v1 values Résultats de fonctions ou évaluations d'expressions
4-7 a0-a4 arguments Les 4 premiers arguments d'une fonction
Non préservé par un appel de fonction
8-15
24-25
t0-t7
t8-t9
temporaries Sauvegardé par l'appelant si besoin
Non préservé par un appel de fonction
16-23 s0-s7 saved values Sauvegardé par l'appelé si besoin
Préservé par un appel de fonction
26-27 k0-k1 kernel Réservé pour interruptions et trap handler
28 gp global pointer Adresse de base pour les données statiques
29 sp stack pointer Adresse du sommet de pile
30 fp frame pointer Adresse de base du cadre d'appel d'une fonction
Préservé par un appel de fonction
31 ra return address Adresse de retour de fonction
Préservé par un appel de fonction

Instructions

Format des instructions


Les instructions MIPS sont toutes codées sur 32 bits et se déclinent synthétiquement suivant 3 formes :
6 bits 5 bits 5 bits 5 bits 5 bits 6 bits
format R OpCode Reg2 Reg3 Reg1 Shift SubCode
format I OpCode Reg2 Reg1 Immediate 16 bits
format J OpCode Adresse 26 bits

Instructions d'accès mémoire et chargement de registre


Ces instructions sont les instructions qui permettent le transfert entre la mémoire (RAM) et les registres. Les autres instructions MIPS (calcul, saut,...) travaillent uniquement avec les registres du processeur.
On inclut aussi dans cette section, les instructions réalisant la copie entre registres ou le chargement de registres avec des valeurs constantes ou des labels (adresses statiques).
lw $R0, RAM # Load word, copie 4 octets de RAM dans $R0 sw $R0, RAM # Store word, copie 4 octets de $R0 dans RAM lb $R0, RAM # Load byte, copie 1 octet de RAM dans $R0 (signed low-order) sb $R0, RAM # Store byte, copie 1 octet (low-order) de $R0 dans RAM lbu $R0, RAM # Load byte unsigned copie 1 octet de RAM # dans $R0 (0-extended low-order) lui $R0, value # Load Upper Immediate copie la constante value dans $R0 # avec décalage de 16 bits li $R0, value # Load Immediate, copie la constante value dans $R0 # pseudo-instruction suivant 16bits signé ou non ou 32 bits la $R0, label # Load Address, copie l'adresse du label dans $R0 # pseudo pour "lui $1, label>>16 ori $R0, $1, label&0xFFFF" move $R0, $R1 # copie le registre $R1 dans le registre $R0 # pseudo pour "addu $R0, $zero, $R1"
Référence indirecte et indexée à la mémoire (RAM)

Dans les instructions Load/Store, la référence à la mémoire utilise un format général RAM = value($R1), où $R1 est un registre contenant une adresse mémoire de base et value est une constante entière signée utilisée comme décalage (offset) par rapport à l'adresse de base. Par exemple, l'adresse notée -12($t1) a pour valeur RAM[$t1-12].
N.B. La notation 0($R1) peut être abrégée par ($R1)

Exemple
.data var1: .word 20 # int var1=20 array1: .space 12 # int[3] array1 .text start: la $t0, array1 # $t0 = adresse de base du tableau li $t1, 5 # $t1 = 5 sw $t1, ($t0) # array1[0] = $t1, adresse de array1[0]=$t0+0 li $t1, 13 # $t1 = 13 sw $t1, 4($t0) # array1[1] = $t1, adresse de array1[1]=$t0+4 la $t2, var1 # $t2 = adresse var1 lw $t1, ($t2) # $t1 = valeur de var1 # Pseudo possible : "lw $t1, var1") sw $t1, 8($t0) # array1[2] = $t1, adresse de array1[2]=$t0+8 move $t3, $t1 # $t3 = $t1

Instructions de calcul


Les opérandes (souvent 3) sont des registres ou éventuellement des constantes (immediate).
# arithmétique add $R0, $R1, $R2 # $R0 = $R1 + $R2; entiers avec signe addi $R0, $R1, -42 # $R0 = $R1 + -42; entiers et constantes signés addu $R0, $R1, $R2 # $R0 = $R1 + $R2; entiers non signés addiu $R0, $R1, 666 # $R0 = $R1 + 666; entiers non signée sub $R0, $R1, $R2 # $R0 = $R1 - $R2; entiers avec signe subu $R0, $R1, $R2 # $R0 = $R1 - $R2; entiers non signés mult $R1, $R2 # [Hi,Lo] = $R1 * $R2; [Hi,Lo] registre spécial 64 bits div $R1, $R2 # Lo = $R1 / $R2, Hi = $R1 mod $R2; mfhi $R0 # $R0 = Hi; mflo $R0 # $R0 = Lo; # Logique binaire and $R0, $R1, $R2 # $R0 = Bitwise AND($R1, $R2) or $R0, $R1, $R2 # $R0 = Bitwise OR($R1, $R2) # Décalage bit sll $R0, $R1, 5 # $R0 = shift left 5 bits from $R1 (= multiply 2^5) srl $R0, $R1, 7 # $R0 = shift right 7 bits from $R1 # Comparaison ("Set if") slt $R0, $R1, $R2 # $R0 = 1 si $R1 < $R2, 0 sinon sltu $R0, $R1, $R2 # $R0 = 1 si 0 <= $R1 < $R2, 0 sinon seq $R0, $R1, $R2 # $R0 = 1 si $R1 == $R2, 0 sinon (pseudo-instruction)

Instructions de saut


Branchements Conditionnels :
beq $R0, $R1, label # saute au label si $R0 == $R1 bne $R0, $R1, label # saute au label si $R0 != $R1 bgez $R1, label # saute au label si $R1 >= $zero bgtz $R1, label # saute au label si $R1 > $zero blez $R1, label # saute au label si $R1 <= $zero bltz $R1, label # saute au label si $R1 < $zero
Sauts inconditionnels et appels de fonctions :
j label # Jump saute au label jal label # Jump And Link saute au label (de fonction) et # copie le compteur d'instruction dans le registre $ra jr $R0 # Jump Register saute à l'adresse contenu dans le registre $R0 jr $ra # saute à l'adresse de retour de fonction positionnée par "jal"
N.B. : En cas d'appel récursif, le registre $ra doit être sauvegardé/restauré pour ne pas être écrasé par un appel jal

Appels Systèmes

Les appels systèmes de MIPS réalisent les interactions entre l'exécutable et le système d'exploitation : Entrées/Sorties, allocation mémoires, exit, heure,... MIDI,... L'appel est réalisé par l'instruction syscall, le type d'appel est préalablement stocké dans le registre "$v0", et les éventuels arguments dans les registres "$a0-a4" (à sauvegarder si besoin!). En sortie, le résultat éventuel de l'appel utilisera les registres "$v0", "$v1".

Les principaux appels systèmes sont :
Service code dans $v0 Arguments en entrée Résultat en sortie
Print Integer 1 $a0=entier à imprimer
Print String 4 $a0=adresse de la chaîne à imprimer
La chaîne termine par \0
Read Integer 5 $v0=entier lu
Read String (fgets) 8 $a0=adresse du buffer d'entrée
$a1=taille du buffer
chaîne à l'adresse $a0
La chaîne terminée par \0
Sbrk (malloc)
Allocate heap memory
9 $a0=nombre d'octets à allouer $v0=adresse mémoire allouée
et non initialisée
Exit 10
Print Character 11 $a0=caractère à imprimer
Read Character 12 $v0=caractère lu
Exit(n) 17 $a0=valeur de retour de l'exécution exit status du processus
Exemples :
# Imprimer un entier contenu dans $t0 li $v0, 1 # syscall code = 1 (print integer) move $a0, $t0 # set argument $a0: $a0 = $t0 syscall # appel l'OS pour imprimer
# Lire un entier au clavier : readline() et parseint() li $v0, 5 # syscall code = 5 (read integer) syscall # appel l'OS pour lire move $v0, $t0 # entier lu dans v0, est sauvé dans $t0
# Imprimer une chaine avec fin de ligne li $v0, 4 # syscall code = 4 (print string) la $a0, str1 # $a0 = adresse de la chaine syscall # imprime str1 li $v0, 11 # syscall code = 11 (print char) li $a0, 10 # $a0 = `\n` syscall # imprime la fin de ligne .data str1 .asciiz "Print this" # la chaine
# Exit(n) li $v0, 17 # syscall code = 17 (exit with status) li $a0, 666 # $a0=666; syscall # exit(666);

CSC 4536, TELECOM SudParis, P Hennequin, Last modified: Oct 2021