почему мы не можем сразу переместить 64-битное значение в память?

Сначала меня немного смущает разница между movq и movabsq, в моем учебнике написано:

Обычная инструкция movq может иметь только непосредственные исходные операнды, которые могут быть представлены как 32-битные числа с дополнением до двух. Затем это значение расширяется на знак, чтобы получить 64-битное значение для пункта назначения. Команда movabsq может иметь произвольное 64-битное непосредственное значение в качестве исходного операнда и может иметь только регистр в качестве назначения.

У меня к этому два вопроса.

Вопрос 1

Инструкция movq может иметь только непосредственные исходные операнды, которые могут быть представлены как 32-битные числа с дополнением до двух.

так это означает, что мы не можем сделать

movq    $0x123456789abcdef, %rbp

и мы должны сделать:

movabsq $0x123456789abcdef, %rbp

но почему movq разработан так, чтобы не работать с 64-битным немедленным значением, что действительно противоречит цели q (слово quard), и нам нужно иметь еще один movabsq только для этой цели, разве это не проблема?

вопрос 2

Поскольку адресатом movabsq должен быть регистр, а не память, поэтому мы не можем переместить 64-битное немедленное значение в память как:

movabsq $0x123456789abcdef, (%rax)

но есть обходной путь:

movabsq $0x123456789abcdef, %rbx
movq    %rbx, (%rax)   // the source operand is a register, not immediate constant, and the destination of movq can be memory

так почему же правило создано, чтобы усложнять жизнь?


person amjad    schedule 07.07.2020    source источник
comment
Обратите внимание, что movq $0xFFFFFFFFFFFFFFFF, (%rax) кодируется, потому что старшие 32 бита соответствуют биту №32. All-F = все-единицы, что совпадает с -1 в дополнении до 2. Что-то вроде 0x12345678abcd, которое имеет более 32 значащих битов, будет работать в качестве примера. (И легче грокнуть, чем просто отказаться от одного из F).   -  person Peter Cordes    schedule 07.07.2020
comment
Также обратите внимание, что GAS собирает movq $0x123456789abcdef, %rbp в тот же машинный код, что и movabsq. Он сразу же замечает, что число не помещается в 32-битное, и автоматически выбирает 64-битный, потому что это возможно для адресата регистра. (Он не делает этого автоматически для констант времени ассемблера, которые еще не были определены, или для адресов, потому что адреса иногда могут быть 32-битными. Поэтому явная запись movabs по-прежнему иногда необходима.) Все это не связано с однако актуальный вопрос, почему у вас не может быть места назначения в памяти.   -  person Peter Cordes    schedule 07.07.2020
comment
Краткий ответ на вопрос, почему мы не можем, заключается в том, что это не предусмотрено в наборе инструкций. Длинный ответ попытался бы оправдать почему, но на самом деле это относится к выбору дизайна, сделанному давным-давно.   -  person Erik Eidt    schedule 07.07.2020


Ответы (2)


Да, переместитесь в регистр, а затем в память для немедленных действий, которые не подходят для 32-разрядной версии с расширенным знаком, в отличие от -1 aka 0xFFFFFFFFFFFFFFFF. Тем не менее, часть почему представляет собой интересный вопрос:


Помните, что asm позволяет делать только то, что возможно в машинном коде. Таким образом, это действительно вопрос о дизайне ISA. Такие решения часто связаны с тем, что оборудование легко декодирует, а также с соображениями эффективности кодирования. (Использовать коды операций в редко используемых инструкциях было бы плохо.)

Он не предназначен для того, чтобы усложнять задачу, он предназначен для того, чтобы не требовать никаких новых кодов операций для mov. А также для ограничения 64-битных непосредственных команд одним специальным форматом инструкций. mov - единственная инструкция, которая может когда-либо использовать 64-битный немедленный вообще (или 64-битный абсолютный адрес для загрузки / сохранения AL / AX / EAX / RAX).

Ознакомьтесь с руководством Intel для форм mov (обратите внимание, что он использует синтаксис Intel, сначала назначение, и мой ответ.) Я также резюмировал формы (и длину их инструкций) в Разница между movq и movabsq в x86-64, как и @MargaretBloom в ответ на В чем разница между командами x86-64 AT&T movq и movabsq?.

Разрешение imm64 вместе с режимом адресации ModR / M также позволило бы довольно легко достичь 15-байтового верхнего предела длины инструкции, например Код операции REX + + imm64 составляет 10 байтов, а ModRM + SIB + disp32 - 6. Таким образом, mov [rdi + rax*8 + 1234], imm64 не будет кодироваться, даже если был код операции для mov r/m64, imm64.

И это при условии, что они перепрофилировали один из однобайтовых кодов операций, которые были освобождены, сделав некоторые инструкции недействительными в 64-битном режиме (например, aaa), что может быть неудобно для декодеров (и предварительных декодеров длины инструкций), потому что в других режимы, в которых эти коды операций не принимают байт ModRM или немедленное выполнение.


movq предназначен для форм mov с обычным байтом ModRM, чтобы разрешить произвольный режим адресации в качестве пункта назначения. (или в качестве источника для movq r64, r/m64). AMD решила оставить для них 32-разрядную версию сразу же, как и при 32-разрядном размере операнда 1.

Эти формы mov имеют тот же формат инструкций, что и другие инструкции, такие как add. Для простоты декодирования это означает, что префикс REX не изменяет длину инструкции для этих кодов операций. Декодирование длины инструкции уже достаточно сложно, когда режим адресации имеет переменную длину.

Итак, movq имеет 64-битный размер операнда, но в остальном тот же формат инструкции mov r/m64, imm32 (становится формой с расширенным знаком, такой же, как и любая другая инструкция, которая имеет только одну непосредственную форму), и mov r/m64, r64 или mov r64, r/m64.

movabs - это 64-разрядная форма существующей краткой формы no-ModRM mov reg, imm32. Это уже особый случай (из-за кодирования no-modrm, с номером регистра из младших 3 бит байта кода операции). Небольшие положительные константы могут просто использовать 32-битный размер операнда для неявного нулевого расширения до 64-битного без потери эффективности (например, 5-байтовый mov eax, 123 / AT&T mov $123, %eax в 32-битном или 64-битном режиме). И наличие 64-битного абсолютного mov полезно, так что это логично, что AMD сделала это.

Поскольку байта ModRM нет, он может кодировать только адресат регистра. Чтобы добавить форму, которая могла бы принимать операнд памяти, потребовался бы совершенно другой код операции.


Из одного POV, будьте благодарны, вы получаете mov с 64-битными немедленно вообще; RISC ISA, такие как AArch64 (с 32-разрядными инструкциями фиксированной ширины), нуждаются в более чем 4 инструкции только для того, чтобы получить 64-разрядное значение в регистре. (Если это не повторяющийся битовый шаблон; AArch64 на самом деле довольно крутой. В отличие от более ранних RISC, таких как MIPS64 или PowerPC64)

Если AMD64 собиралась ввести новый код операции для mov, mov r/m, sign_extended_imm8 было бы гораздо более полезным для экономии размера кода. Компиляторы нередко могут выдавать несколько mov qword ptr [rsp+8], 0 инструкций для обнуления локального массива или структуры. , каждый из которых содержит 4-байтовый 0 немедленный. Помещение ненулевого малого числа в регистр довольно распространено и сделает mov eax, 123 3-байтовую инструкцию (меньше 5), а mov rax, -123 4-байтовую инструкцию (меньше 7). Это также сделало бы обнуление регистра без затирания FLAGS 3 байта.

Разрешение mov imm64 в память было бы полезным достаточно редко, поэтому AMD решила, что не стоит усложнять декодеры. В этом случае я согласен с ними, но AMD была очень консервативна при добавлении новых кодов операций. Так много упущенных возможностей убрать бородавки x86, вроде расширения setcc было бы неплохо. Но я думаю, что AMD не была уверена, что AMD64 завоюет популярность, и не хотела зависать, нуждаясь в большом количестве дополнительных транзисторов / мощности для поддержки функции, если люди ее не используют.

Сноска 1:
В общем, 32-битное немедленное преобразование - довольно хорошее решение для размера кода. Очень редко нужно add немедленно обратиться к чему-то, что выходит за пределы диапазона + -2 ГиБ. Это может быть полезно для побитовых вещей, таких как AND, но для установки / очистки / переворота одного бита хороши инструкции bts / btr / btc (принятие битовой позиции как 8-битное немедленно, вместо необходимости маски). Вы не хотите, чтобы sub rsp, 1024 была 11-байтовой инструкцией; 7 уже достаточно плохо.


Гигантские инструкции? Не очень эффективно

В то время, когда разрабатывалась AMD64 (начало 2000-х), процессоры с кэшем uop не использовались. (Intel P4 с кешем трассировки действительно существовал, но в ретроспективе это было расценено как ошибка.) Выборка / декодирование инструкций происходит фрагментами размером до 16 байт, поэтому наличие одной инструкции размером почти 16 байт не намного лучше для интерфейс, чем movabs $imm64, %reg.

Конечно, если серверная часть не успевает за клиентской частью, то пузырек, состоящий только из 1 инструкции, декодированной в этом цикле, может быть скрыт путем буферизации между этапами.

Отслеживание такого количества данных для одной инструкции также может стать проблемой. ЦП должен куда-то поместить эти данные, и если есть 64-битное немедленное и 32-битное смещение в режиме адресации, это много бит. Обычно инструкции требуется максимум 64 бита для imm32 + disp32.


Кстати, есть специальные коды операций no-modrm для большинства операций с RAX и немедленным. (x86-64 развился из 8086, где AX / AL был более особенным, см. this для получения дополнительной истории и объяснения). Для тех add/sub/cmp/and/or/xor/... rax, sign_extended_imm32 форм без ModRM было бы вполне правдоподобно использовать вместо этого полный imm64. Самый распространенный случай для RAX, немедленное использование 8-битного расширенного знака (-128..127), во всяком случае не этой формы, и он сохраняет только 1 байт для инструкций, которым требуется 4-байтовый немедленный. Однако, если вам нужна 8-байтовая константа, лучше поместить ее в регистр или память для повторного использования, чем выполнять 10-байтовую и-imm64 в цикле.

person Peter Cordes    schedule 07.07.2020

По первому вопросу:

Из официальной документации ассемблера GNU:

В 64-битном коде movabs может использоваться для кодирования инструкции mov с 64-битным смещением или непосредственным операндом.

mov reg64, imm (в синтаксисе Intel, сначала назначение) - единственная инструкция, которая принимает 64-битное немедленное значение в качестве параметра. Вот почему вы не можете записать 64-битное значение непосредственно в память.


По второму вопросу:

Для других мест назначения, например, для области памяти, 32-битное немедленное сообщение может быть расширено по знаку до 64-битного немедленного действия (что означает, что верхние 33 бита там одинаковы). В этом случае вы используете инструкцию movq.

Это также возможно, если целью является регистр, сохраняющий 3 байта:

C8 B0 FF FF FF 7F 00 00 00 00   movabs $0x7FFFFFFF, %rax
C8 C7 C0 FF FF FF 7F            movq   $0x7FFFFFFF, %rax

В 64-битном режиме 0xFFFFFFFF верхние 33 бита не совпадают, поэтому movl здесь использовать нельзя. Вот почему я выбрал 0x7FFFFFFF в этом примере. Но есть и другой вариант:

При записи в 32-битный регистр (нижняя часть 64-битного регистра) старшие 32-битные регистры обнуляются. Следовательно, для 64-битного немедленного сигнала, старшие 32 бита которого равны нулю, также можно использовать movl, что сохраняет еще один байт:

C7 C0 FF FF FF 7F               movl   $0xFFFFFFFF, %eax

GAS не делает это автоматически, но может выбирать между movabs и movq, если вы используете mov, в зависимости от размера мгновенного сообщения.

Кредит: Спасибо Питеру Кордесу за то, что он заметил, что сначала я что-то напортачил в своем ответе, и добавил дополнительную информацию.

person fcdt    schedule 07.07.2020
comment
Если исправлены некоторые ошибки в вашем ответе, посмотрите сообщение журнала редактирования. Примечательно, что GAS не оптимизирует movq до movl, только между movq и movabsq в зависимости от непосредственного. Вы можете сказать что-нибудь еще. Ваш ответ теперь правильный, но я не уверен, что он полезен. - person Peter Cordes; 07.07.2020
comment
Спасибо, я там что-то напортачил. - person fcdt; 07.07.2020
comment
Да, и даже mov RAX, 0x8765432187654321 будет разбит декодерами на две записи uop. Микроархитектуры оптимизированы для общего случая, 32b и меньше. - person Olsonist; 08.07.2020