Странное поведение ассемблера MIPS с инструкцией перехода (и ссылки)

Итак, мы изучаем архитектуру MIPS в школе и внедряем архитектуру MIPS32. Я думал, что я буду использовать GNU cross-binutils в качестве ассемблера, но я получаю странный вывод при работе с инструкциями jal, j и jr. Кажется, ассемблер вставляет инструкции в неправильных местах. Я понятия не имею, почему это происходит, и я сомневаюсь, что ассемблер MIPS будет так сломан, поэтому я предполагаю, что это должно произойти.

Вот мой фиктивный файл сборки:

.section .text
.globl __start

__start:
   addi $a0, $0, 100
   addi $a1, $0, 200 
   jal test

test:
   add $v0, $a0, $a1
   jr $ra

Однако, когда я разбираю, я получаю этот вывод:

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
    ...

Это какая-то архитектурная причуда? Если да, то в чем причина этого?

РЕДАКТИРОВАТЬ: Протестировано добавление некоторых NOP только для черта ...

.section .text
.globl __start

__start:
   addi $a0, $0, 100
   addi $a1, $0, 200 
   nop
   jal test

test:
   add $v0, $a0, $a1
   nop
   jr $ra

и это дает мне что-то, что кажется несколько правильным.

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

Почему jal и j меняются местами с последней инструкцией?

 schnaader27 сент. 2010 г., 22:19
Выглядит как проблема с порядком байтов внутри компилятора (или дизассемблера), просто на уровне команд, а не на уровне байтов ... странно ...

Ответы на вопрос(1)

Решение Вопроса

MIPS имеет явную опасность для трубопроводов; Инструкция, следующая непосредственно за инструкцией перехода или перехода, всегда будет выполняться (эту инструкцию иногда называют «интервалом задержки перехода»). Если ваш код был действительно собран именно так, как вы его написали:

__start:
   addi $a0, $0, 100
   addi $a1, $0, 200 
   jal test

test:
   add $v0, $a0, $a1
   jr $ra

тогдаadd Инструкция будет выполняться дважды за то время, чтоjal происходит: один раз в интервале задержки и один раз в следующем цикле, когда изменение счетчика программы действительно вступило в силу.

По умолчанию ассемблер GNU переупорядочивает инструкции для вас: ясно, что второйaddi всегда должен выполняться, поэтому его можно поменять местами сjal инструкция, так чтоaddi перемещается в отсек задержки. (В случаях, когда ассемблер не может сделать вывод, что это безопасно, он вставитnop вместо этого в отсек задержки.)

Если вы не хотите, чтобы он делал это для вас, добавьте директиву

.set noreorder

в верхней части вашего исходного файла. В этом случае вы сами должны разобраться с опасностями. Если вы сделаете это, я рекомендую пометить слоты задержки так, чтобы они выделялись, например добавив дополнительный пробел (или два) отступа. Например:

.set noreorder

__start:
   addi $a0, $0, 100
   jal test
     addi $a1, $0, 200 

test:
   add $v0, $a0, $a1
   jr $ra
     nop
 Maister28 сент. 2010 г., 09:22
Понимаю. Благодарю. Наша реализация - один цикл, поэтому я буду использовать .set noreorder :).
 ajxs14 янв. 2019 г., 22:37
Похоже, что набор инструментов GNU binutils фактически делает и то, и другое. Я хотел бы знать обстоятельства, которые решают, заполняет ли это место задержки отделенияnop и когда он вставляет собранную инструкцию в предыдущий слот.
 tc.03 окт. 2010 г., 04:01
Как историческое примечание, компиляторы традиционно просто заполняют интервал задержки перехода nop. Я не уверен, когда все изменилось, чтобы "просто позволить ассемблеру беспокоиться об этом".

Ваш ответ на вопрос