comportement assembleur MIPS bizarre avec saut (et lien) instruction
-
25-09-2019 - |
Question
Alors, nous étudions l'architecture MIPS à l'école et nous mettons en œuvre une architecture MIPS32. Je pensais utiliser croisées binutils GNU comme assembleur mais je suis en train de sortie bizarre quand les instructions concernant jal, j et jr. L'assembleur semble insérer les instructions aux mauvais endroits. Je ne sais pas pourquoi cela arrive, et je doute que l'assembleur MIPS serait que cassé, donc je suppose que cela est censé se produire.
Voici mon fichier de montage fictif:
.section .text
.globl __start
__start:
addi $a0, $0, 100
addi $a1, $0, 200
jal test
test:
add $v0, $a0, $a1
jr $ra
Cependant, quand je démonte je reçois cette sortie:
Disassembly of section .text:
00000000 <__start>:
0: 20040064 addi a0,zero,100
4: 0c000003 jal c <test> <--- Why is jal coming before addi?
8: 200500c8 addi a1,zero,200
0000000c <test>:
c: 03e00008 jr ra <--- Why is jr coming before add?
10: 00851020 add v0,a0,a1
...
Est-ce une bizarrerie architecturale? Si oui, quelle est la raison derrière tout cela?
EDIT: Testé en ajoutant quelques années NOP juste pour le diable ...
.section .text
.globl __start
__start:
addi $a0, $0, 100
addi $a1, $0, 200
nop
jal test
test:
add $v0, $a0, $a1
nop
jr $ra
et il me donne quelque chose qui semble un peu correct.
Disassembly of section .text:
00000000 <__start>:
0: 20040064 addi a0,zero,100
4: 200500c8 addi a1,zero,200
8: 0c000004 jal 10 <test>
c: 00000000 nop
00000010 <test>:
10: 00851020 add v0,a0,a1
14: 03e00008 jr ra
18: 00000000 nop
1c: 00000000 nop
Pourquoi les lieux et j échange jal avec la dernière instruction?
La solution
MIPS a explicitement les risques de pipeline; l'instruction qui suit immédiatement une branche ou d'une instruction de saut sera toujours exécuté (cette instruction est parfois appelée la « fente de retard de branche »). Si votre code a été vraiment assemblé exactement comme vous l'avez écrit:
__start:
addi $a0, $0, 100
addi $a1, $0, 200
jal test
test:
add $v0, $a0, $a1
jr $ra
alors l'instruction de add
serait exécuté deux fois autour du moment où le jal
arrive. Une fois dans la fente de retard, et une fois sur le cycle suivant lorsque le changement de compteur de programme a effectivement pris effet
Par défaut, l'assembleur GNU réordonne instructions pour vous: il est clair que la deuxième addi
doit toujours être exécuté, il peut donc être échangé avec l'instruction jal
, de sorte que les mouvements de addi
dans la fente de retard. (Dans les cas où l'assembleur ne peut pas en déduire qu'il est sûr de le faire, il va insérer un nop
dans la fente de retard à la place.)
Si vous ne voulez pas faire ce réordonnement pour vous, ajoutez la directive
.set noreorder
en haut de votre fichier source. Vous devez faire face aux risques vous-même dans ce cas. Si vous faites cela, je recommande annoter les fentes de retard afin qu'ils se démarquent - par exemple en ajoutant un espace supplémentaire (ou deux) de l'indentation. Par exemple:
.set noreorder
__start:
addi $a0, $0, 100
jal test
addi $a1, $0, 200
test:
add $v0, $a0, $a1
jr $ra
nop