CSC4251_4252 — Compilation : du langage de haut niveau à l'assembleur

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, c.-à-d. 42, ou aussi 0xbabe.
  • Les caractères sont entourés par des simples quote, c.-à-d. 'b'.
  • Les chaînes de caractères sont entourées par des doubles quotes, c.-à-d. "Une chaîne".
  • Les noms de label sont liés au charset de l'environnement JAVA et peuvent donc être en UTF8 ou autre, c.-à-d. Матрёшка ou Matriochka.

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 variables, 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 422 # int var1 = 422; 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 sont 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 2n 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

Registres

On dispose de 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, c.-à-d. de $0 à $31,
  • utilisant un nom conventionnel, p.ex. $sp ou $t1,

Deux registres spéciaux, nommément 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écroît quand la pile grandit.

Voici les registres dont on dispose :

Numéro Nom Sous-titre Description
0 zero zéro 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-a3 Arguments Les 4 premiers arguments d'une fonction
Valeur non préservée par un appel de fonction
8-15
24-25
t0-t7
t8-t9
Temporaries Registre de travail
Valeur non préservée par un appel de fonction
16-23 s0-s7 Saved values Registres de travail
Valeur préservée 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
Valeur préservée par un appel de fonction
31 ra Return Address Adresse de retour de fonction
Valeur préservée par un appel de fonction

Dans le tableau, on remarque que la valeur de certains registres est dite « préservée » ou « non préservée » pendant un appel de fonction :

  • valeur d'un registre préservée par l'appel : la fonction appelée est responsable de ne pas écraser ce registre ou doit faire un backup/restore autour de la fonction si elle veut utiliser ce registre ;
  • valeur d'un registre non préservée par l'appel : l'appelé fait ce qu'il veut de ce registre ; l'appelant fait un backup/restore autour des appels de fonction s'il veut utiliser ce registre.

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 Op Code Reg 2 Reg 3 Reg 1 Shift SubCode
format I Op Code Reg 2 Reg 1 Immediate 16 bits
format J Op Code Adresse 26 bits

En assembleur, le terme « Immediate » désigne une constante entière dont la valeur est codée dans l'instruction plutôt que passée dans un registre. Autrement dit, pour faire court, c'est une constante.

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, etc.) 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).

Dans le tableau qui suit, la formulation « pseudo-instruction suivant 16 ou 32 bits, signé ou non » signifie que la pseudo-instruction génère une séquence d'instructions basiques différentes selon que la constante est codée sur 16 ou 32 bits, et selon que la constante est signée ou non (signe négatif). Cf. l'exemple dans le cours avec la pseudo-instruction li.

Les voici :

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 16 ou 32 bits, signé ou non 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, l'adresse de array1[0] est $t0+0 li $t1, 13 # $t1 = 13 sw $t1, 4($t0) # array1[1] = $t1, l'adresse de array1[1] est $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, l'adresse de array1[2] est $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 # Move From Hi $R0 = Hi; mflo $R0 # Move From Lo $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 # Set on Less Than $R0 = 1 si $R1 < $R2, 0 sinon sltu $R0, $R1, $R2 # Set on Less Than unsigned $R0 = 1 si 0 <= $R1 < $R2, 0 sinon seq $R0, $R1, $R2 # Set on EQual $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, etc., MIDI, etc. 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 utilise les registres $v0 et $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 est terminé 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 est 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 with status 17 $a0=valeur de retour de l'exécution exit status du processus

Voici quatre exemples :

# Exemple 1 : 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

# Exemple 2 : 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

# Exemple 3 : imprimer une chaîne avec fin de ligne li $v0, 4 # syscall code = 4 (print string) la $a0, str1 # $a0 = adresse de la chaîne 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 chaîne

# Exemple 4 : exit(n) li $v0, 17 # syscall code = 17 (exit with status) li $a0, 42 # $a0=42; syscall # exit(42);

Appels Systèmes (Bonus)

Pour terminer, voici deux appels systèmes spécifiques à l'outil Mars :

Service code dans $v0 Arguments en entrée Résultat en sortie
MIDI out Asynchronous 31 $a0=note 0-127
$a1=durée (ms)
$a2=instrument 0-127
$a3=volume 0-127
Joue la note et retour immédiat
MIDI out Synchronous 33 $a0=note 0-127
$a1=durée (ms)
$a2=instrument 0-127
$a3=volume 0-127
Joue la note et retour après la durée

  • Note de 0 à 127. Note 57 = 440Hz = la3 (A4). Douze notes par octave.
  • Instruments de 0 à 127, 16 familles de 8 instruments :
    • Piano(0-7), Percussions chromatiques(8-15), Orgues(16-23), Guitares(24-31)
    • Basses(32-39), Cordes(40-47), Orchestre(48-55), Cuivres(56-63)
    • Anches(64-71), Flutes(72-79), Synthétiseur solo(80-87), Nappes de synthétiseur(88-95)
    • Effets de synthétiseur(96-103), Instruments ethniques(104-111), Percussions(112-119), Effets sonores (120-127)
    • cf. Liste détaillée

Un exemple "Apéro==Canon de MIDI": midiCanon.mips.


CSC4251_4252, Télécom SudParis, Pascal Hennequin, Denis Conan, Paul Gibson Last modified: November 2024