Почему MIPS использует R0 в качестве «нуля», когда вы можете просто XOR двух регистров для получения 0?

10

Я думаю, что я ищу ответ на вопрос о пустяках. Я пытаюсь понять, почему архитектура MIPS использует явное «нулевое» значение в регистре, когда вы можете достичь того же, просто XOR'и любой регистр против самого себя. Можно сказать, что операция уже сделана для вас; тем не менее, я не могу себе представить ситуацию, когда вы использовали бы много «нулевых» значений. Я читаю оригинальные статьи Хеннесси, и это просто присваивает ноль на самом деле без какого-либо реального оправдания.

Существует ли логическая причина иметь жестко запрограммированное двоичное присвоение нуля?

обновление: в 8k исполняемого файла из xc32-gcc для ядра MIPS в PIC32MZ у меня есть один экземпляр "ноль".

add     t3,t1,zero

фактический ответ: я присудил награду человеку, который имел информацию о MIPS и кодах условий. Ответ на самом деле лежит в архитектуре MIPS для условий. Хотя изначально я не хотел выделять на это время, я рассмотрел архитектуру для opensparc , MIPS-V и OpenPOWER (этот документ был внутренним), и вот краткие выводы. Регистр R0 необходим для сравнения по веткам из-за архитектуры конвейера.

  • целочисленное сравнение с нулем и ответвлением (bgez, bgtz, blez, bltz)
  • целочисленное сравнение двух регистров и ветви (beq, bne)
  • целочисленное сравнение двух регистров и trap (teq, tge, tlt, tne)
  • регистр сравнения целых чисел и немедленный и trap (teqi, tgei, tlti, tnei)

Это просто сводится к тому, как аппаратное обеспечение выглядит в реализации. Из руководства MIPS-V на странице 68 есть ссылка, на которую нет ссылок:

Условные ветви были разработаны так, чтобы включать операции арифметического сравнения между двумя регистрами (как это также делается в PA-RISC и Xtensa ISA), а не использовать коды условий (x86, ARM, SPARC, PowerPC) или сравнивать только один регистр с нулем ( Альфа, MIPS) или два регистра только для равенства (MIPS). Эта конструкция была мотивирована наблюдением, что объединенная команда сравнения и ветвления ts в обычный конвейер позволяет избежать дополнительного состояния кода условия или использования временного регистра и уменьшает размер статического кода и динамическую трассировку выборки команд. Другой момент заключается в том, что для сравнения с нулем требуется нетривиальная задержка контура (особенно после перехода к статической логике в сложных процессах), и поэтому она почти такая же дорогая, как и арифметическая величина. Другое преимущество объединенной команды сравнения и ветвления состоит в том, что ответвления наблюдаются раньше в потоке внешних команд и поэтому могут быть предсказаны ранее. Возможно, есть преимущество для схемы с кодами условий в том случае, когда можно использовать несколько ветвей на основе одних и тех же кодов условий, но мы считаем, что этот случай является относительно редким.

Документ MIPS-V не попадает в автора цитируемого раздела. Я благодарю всех за их время и внимание.

B Degnan
источник
6
Вы часто хотите использовать 0-значный регистр в какой-либо операции в качестве исходного значения. Перед выполнением этих операций было бы непросто обнулить регистр, поэтому производительность выиграет, если вы сможете просто использовать предоставленный ноль вместо того, чтобы создавать его самостоятельно, когда он вам нужен. Примеры включают добавление флага переноса.
JimmyB
3
В архитектуре AVR gcc заботится об инициализации r1 в ноль при запуске и никогда не касается этого значения снова, используя r1 в качестве источника везде, где нельзя использовать непосредственный 0. Здесь выделенный нулевой регистр «эмулируется» программным обеспечением компилятором по соображениям производительности. (Большинство AVR имеют 32 регистра, поэтому откладывание одного (двух) фактически не требует больших затрат в связи с возможными преимуществами производительности и размера кода.)
JimmyB
1
Я не знаю о MIPS, но может быть быстрее переместить r0 в другой регистр по сравнению с XORing этого регистра, чтобы очистить его.
JimmyB
Таким образом, вы не согласны с тем, что ноль так часто, что он стоит в регистре? Тогда , наверное , вы правы , потому что это правда , это спорно и есть много ИСАСА предпочитает не резервирует нулевой регистра. Как и другие спорные функции того времени, такие как окна регистрации, временные интервалы, предопределение инструкций из «старых дней» ... если вы хотите создать ISA, вам не нужно использовать их, если вы решите этого не делать.
user3528438
2
Может быть интересно прочитать одну из старых статей RISC Беркли, RISC I: Компьютер с VLSI с сокращенным набором инструкций . В нем показано, как использование жесткого проводного нулевого регистра R0 позволяет реализовать несколько инструкций VAX и режимов адресации в одной инструкции RISC.
Марк Плотник

Ответы:

14

Нулевой регистр на процессорах RISC полезен по двум причинам:

Это полезная константа

В зависимости от ограничений ISA, вы не можете использовать литерал в кодировке некоторых инструкций, но вы можете быть уверены, что можете использовать его r0для получения 0.

Может использоваться для синтеза других инструкций

Это, пожалуй, самый важный момент. Как разработчик ISA, вы можете обменять регистр общего назначения на нулевой регистр, чтобы иметь возможность синтезировать другие полезные инструкции. Синтезировать инструкции хорошо, потому что, имея меньше фактических инструкций, вам нужно меньше битов для кодирования операции в коде операции, что освобождает пространство в пространстве кодирования команд. Вы можете использовать это пространство, чтобы иметь, например, большие смещения адресов и / или литералы.

Семантика нулевого регистра аналогична /dev/zeroсистемам * nix: все записанное в него отбрасывается, и вы всегда читаете обратно 0.

Давайте рассмотрим несколько примеров того, как мы можем создавать псевдоинструкции с помощью r0нулевого регистра:

; ### Hypothetical CPU ###

; Assembler with syntax:
; op rd, rm, rn 
; => rd: destination, rm: 1st operand, rn: 2nd operand
; literal as #lit

; On an CPU architecture with a status register (which contains arithmetic status
; flags), `sub` can be used, with r0 as destination to discard result.
cmp rn, rm     ; => sub r0, rn, rm

; `add` instruction can be used as a `mov` instruction:
mov rd, rm     ; => add rd, rm, r0
mov rd, #lit   ; => add rd, r0, #lit

; Negate:
neg rd, rm     ; => sub rd, r0, rm

; On CPU without status flags,
nop            ; => add r0, r0, r0

; RISC-V's `jal` instruction -- Jump and Link: Jump to PC-relative instruction,
; save return address into rd; we can synthesize a `jmp` instruction out of it.
jmp dest       ; => jal r0, dest

; You can even load from an absolute (direct) address, for a usually small range
; of addresses by using a literal offset as an address.
ld rd, addr    ; => ld rd, [r0, #addr]

Дело о MIPS

Я более внимательно посмотрел на набор инструкций MIPS. Есть несколько псевдоинструкций, которые используют $zero; они в основном используются для филиалов. Вот несколько примеров того, что я нашел:

move $rt, $rs          => add $rt, $rs, $zero

not $rt, $rs           => nor $rt, $rs, $zero

b Label                => beq $zero, $zero, Label ; a small relative branch

bgt $rs, $rt, Label    => slt $at, $rt, $rs
                          bne $at, $zero, Label

blt $rs, $rt, Label    => slt $at, $rs, $rt
                          bne $at, $zero, Label

bge $rs, $rt, Label    => slt $at, $rs, $rt
                          beq $at, $zero, Label

ble $rs, $rt, Label    => slt $at, $rt, $rs
                          beq $at, $zero, Label

Что касается того, почему вы нашли только один экземпляр $zeroрегистра в вашей разборке, возможно, это ваш дизассемблер, который достаточно умен, чтобы преобразовать известные последовательности команд в их эквивалентную псевдоинструкцию.

Нулевой регистр действительно полезен?

Что ж, очевидно, ARM считает наличие нулевого регистра достаточно полезным, что в их (несколько) новом ядре ARMv8-A, которое реализует AArch64, теперь есть нулевой регистр в 64-битном режиме; раньше не было нулевого регистра. (Регистр немного особенный, хотя, в некоторых контекстах кодирования это нулевой регистр, в других он вместо этого обозначает указатель стека )

Jarhmander
источник
Я не думаю, что MIPS использует флаги, не так ли? Нулевой регистр добавляет возможность безоговорочно выполнять чтение / запись определенных адресов, не обращая внимания на содержимое любых регистров ЦП, и помогает упростить операцию в стиле «немедленного перемещения», но другие перемещения могут быть выполнены путем логического перемещения источника самостоятельно. ,
суперкат
1
В самом деле, не существует регистра , которые держат флаги арифметических, вместо этого есть три команды , которые помогают эмулировать общие условные ветви ( slt, slti, sltu).
Jarhmander
Глядя на набор инструкций MIPS и учитывая, что из того, что я понимаю, каждая инструкция будет извлечена ко времени выполнения предыдущей инструкции, я задаюсь вопросом, не было бы сложно иметь код операции, который ничего не делает напрямую, а вместо этого говорит, что если выполняется команда непосредственного режима, и следующая извлеченная команда имеет этот битовый шаблон, то верхние 16 битов операнда будут взяты из предварительно выбранной инструкции? Это будет 32-битные операции непосредственного режима, которые будут обрабатываться с помощью
двухсловной
... загрузка операнда, а затем третий цикл для его фактического использования.
суперкат
7

Большинство реализаций ARM / POWER / SPARC имеют скрытый регистр RAZ

Вы можете подумать, что ARM32, SPARC и т. Д. Не имеют регистра 0, но на самом деле они есть! На уровне микроархитектуры большинство инженеров-проектировщиков ЦП добавляют регистр 0, который может быть невидим для программного обеспечения (нулевой регистр ARM невидим), и используют этот нулевой регистр для упрощения декодирования команд.

Рассмотрим типичный современный дизайн ARM32, который имеет программный невидимый регистр, скажем, R16, подключенный к 0. Рассмотрим загрузку ARM32, во многих случаях инструкция загрузки ARM32 попадает в одну из этих форм (некоторое время игнорируйте предварительную индексацию, чтобы сохранить обсуждение простым ) ...

LDR ra, [rb] // NOTE:The ! is optional and represents address writeback.
LDR ra, [rb, rc](!)
LDR ra, [rb, #k](!)

Внутри процессора это декодирует в общий

ldr.uop ra, rb, rx, rc, #c // Internal decoded instruction format.

перед входом в стадию выдачи, где читаются регистры. Обратите внимание, что rx представляет регистр для обратной записи обновленного адреса. Вот несколько примеров декодирования:

LDR R0, [R1]      ==> ldr.uop R0, R1, R16, R16, #0 // Writeback to NULL. 
LDR R0, [R1, R2]! ==> ldr.uop R0, R1, R1, R2,   #0 // Writeback to R1.
LDR R0, [R1, #2]  ==> ldr.uop R0, R1, R16, R16, #2 // Writeback to NULL.

На уровне цепи все три нагрузки фактически являются одной и той же внутренней инструкцией, и простой способ получить такую ​​ортогональность состоит в создании наземного регистра R16. Поскольку R16 всегда заземлен, эти инструкции, естественно, правильно декодируются без какой-либо дополнительной логики. Отображение класса инструкций в единый внутренний формат очень помогает в суперскалярных реализациях, поскольку уменьшает сложность логики.

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

Большинство реализаций процессоров, независимо от архитектуры, заканчиваются моделью регистра RAZ на ранней стадии разработки. Конвейер MIPS, по сути, начинается с точки, которая в других архитектурах будет в несколько этапов.

MIPS сделал правильный выбор

Таким образом, регистр «чтение как ноль» является почти обязательным в любой современной реализации процессора, и MIPS, делающий его видимым для программного обеспечения, безусловно, является плюсом, учитывая, как он оптимизирует внутреннюю логику декодирования. Разработчикам процессоров MIPS не нужно добавлять дополнительный регистр RAZ, поскольку $ 0 уже на земле. Поскольку RAZ доступен для ассемблера, для MIPS доступно множество инструкций psuedo, и это можно рассматривать как передачу части логики декодирования самому ассемблеру вместо создания выделенных форматов для каждого типа команд, чтобы скрыть регистр RAZ из программного обеспечения. как с другими архитектурами. Регистр RAZ - хорошая идея, и поэтому ARMv8 скопировал его.

Если бы ARM32 имел регистр $ 0, логика декодирования стала бы проще, а архитектура была бы намного лучше с точки зрения скорости, площади и мощности. Например, из трех представленных выше версий LDR потребуются только два формата. Точно так же нет необходимости резервировать логику декодирования для команд MOV и MVN. Кроме того, CMP / CMN / TST / TEQ станет избыточным. Также не было бы необходимости проводить различие между коротким (MUL) и длинным умножением (UMULL / SMULL), поскольку короткое умножение можно рассматривать как длинное умножение с высоким регистром, установленным в $ 0 и т. Д.

Поскольку MIPS изначально разрабатывался небольшой командой, важна была простота проектирования, и поэтому $ 0 был явно выбран в духе RISC. ARM32 сохраняет множество традиционных функций CISC на архитектурном уровне.

Ревант Камарадж
источник
1
Не все процессоры ARM32 работают так, как вы описали. Некоторые имеют более низкую производительность для более сложных команд загрузки и / или для обратной записи в регистр. Таким образом, они не могут декодировать все одинаково.
Питер Кордес
6

Отказ от ответственности: я действительно не знаю ассемблера MIPS, но регистр 0-значений не уникален для этой архитектуры, и я думаю, что он используется так же, как и в других известных мне архитектурах RISC.

XOR для регистра для получения 0 будет стоить вам одну инструкцию, в то время как использование предопределенного 0-значного регистра не будет.

Например, mov RX, RYинструкция часто реализуется как add RX, RY, R0. Без 0-значного регистра вам придется xor RZ, RZкаждый раз, когда вы хотите использовать mov.

Другим примером является cmpинструкция и ее варианты (например, «сравнить и перейти», «сравнить и переместить» и т. Д.), Где cmp RX, R0используется для проверки на отрицательные числа.

Дмитрий Григорьев
источник
1
Будут ли какие-либо проблемы с реализацией MOV Rx,Ryкак AND Rx,Ry,Ry?
суперкат
3
@supercat Вы не сможете кодировать mov RX, Immили mov RX, mem[RY]если ваш набор команд поддерживает только одно непосредственное значение и один доступ к памяти для каждой инструкции.
Дмитрий Григорьев
Я не знаю, какие режимы адресации имеет MIPS. Я знаю, что ARM имеет режимы [Rx + Ry << scale] и [Rx + disp], и, хотя возможность использовать последний для некоторых абсолютных адресов может быть полезна в некоторых случаях, это, как правило, несущественно. Прямой режим [Rx] можно эмулировать с помощью [Rx + disp], используя нулевое смещение. Что использует MIPS?
суперкат
movплохой пример; Вы могли бы реализовать это с непосредственным 0 вместо нулевого регистра. например ori dst, src, 0. Но да, вам понадобится код операции для mov-немедленного, чтобы зарегистрироваться, если у вас его нет addiu $dst, $zero, 1234, например, luiдля младших 16 бит вместо верхних 16. И вы не можете использовать norилиsub создать один операнд, а не / neg ,
Питер Кордес
@supercat: если вам все еще интересно: классический MIPS имеет только один режим адресации: register + disp16. Современные MIPS добавили другие коды операций для двухрежимных режимов адресации для загрузки / сохранения FP, ускоряя индексацию массива. (Но все же не для целочисленной загрузки / хранения, возможно потому, что для этого может потребоваться больше портов чтения в целочисленном регистровом файле для двух регистров адресов + регистр данных для хранилища. См. Использование регистра в качестве смещения )
Питер Кордес
3

Привязка нескольких выводов к земле в конце вашего регистрационного банка стоит дешево (дешевле, чем сделать его полноценным регистром).

Выполнение фактического xor требует немного энергии и времени, чтобы переключить ворота и затем сохранить их в реестре, зачем платить эту стоимость, если существующее значение 0 может быть легко доступно.

Современные процессоры также имеют (скрытый) регистр с 0 значениями, которые они могут использовать в результате выполнения xor eax eaxинструкции через переименование регистров.

чокнутый урод
источник
6
Реальная стоимость R0заключается не в заземлении нескольких проводов, а в том, что вы должны зарезервировать для него код в каждой инструкции, которая имеет дело с регистрами.
Дмитрий Григорьев
Xor - это красная сельдь. Обнуление по xor подходит только для x86, где процессоры распознают идиомы и избегают зависимости от входных данных. Как вы указали, семья Sandybridge даже не запускает для этого моп, а просто обрабатывает его на этапе регистрации-переименования. ( Каков наилучший способ установить регистр в ноль в сборке x86: xor, mov или и? ). Но на MIPS XOR в регистре будет иметь ложную зависимость; Правила упорядочения зависимостей в памяти (HW-эквивалент C ++ std::memory_order_consume) требуют XOR для распространения зависимости.
Питер Кордес
Если бы у вас не было нулевого регистра, вы бы добавили код операции для немедленного перемещения в регистр. Как, luiно не смещено влево на 16. Таким образом, вы все равно можете поместить небольшое число в регистр с одной инструкцией. Разрешение только нуля с ложной зависимостью было бы безумием. (Обычный MIPS создает ненулевые значения с помощью addiu $dst, $zero, 1234или ori, поэтому ваш аргумент «затраты на электроэнергию» не работает. Если вы хотите избежать запуска ALU, вы должны добавить код операции для mov-немедленного для регистрации вместо программного обеспечения ADD или OR немедленное с нуля.)
Питер Кордес