64-битная сборка, когда использовать регистры меньшего размера

Я понимаю, что в сборке x86_64 есть, например, (64-битный) регистр rax, но он также может быть доступен как 32-битный регистр, eax, 16-битный, топор и 8-битный, al. В какой ситуации я не просто использовал бы полные 64 бита, и почему, какое бы это было преимущество?

Как пример, с этой простой программой hello world:

section .data
msg: db "Hello World!", 0x0a, 0x00
len: equ $-msg

section .text
global start

start:
mov rax, 0x2000004      ; System call write = 4
mov rdi, 1              ; Write to standard out = 1
mov rsi, msg            ; The address of hello_world string
mov rdx, len            ; The size to write
syscall                 ; Invoke the kernel
mov rax, 0x2000001      ; System call number for exit = 1
mov rdi, 0              ; Exit success = 0
syscall                 ; Invoke the kernel

rdi и rdx, по крайней мере, нужны только 8 бит, а не 64, верно? Но если я изменю их на dil и dl соответственно (их младшие 8-битные эквиваленты), программа собирает и связывает, но ничего не выводит.

Тем не менее, это все еще работает, если я использую eax, edi и edx, поэтому я должен использовать их, а не полные 64-битные? Почему или почему нет?

5 ответов

Решение

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

MOV AL, [0x1234]

против

MOV RAX, [0x1234]
SHR RAX, 56
# assuming there are actually 8 accessible bytes at 0x1234,
# and they're the right endianness; otherwise you'd need
# AND RAX, 0xFF or similar...

Или, конечно, записать указанное значение обратно в память.


(Редактировать, как 6 лет спустя):

Так как это продолжает появляться:

MOV AL, [0x1234]
  • читает только один байт памяти в 0x1234 (обратное будет перезаписывать только один байт памяти)
  • сохраняет то, что было в других 56 битах RAX
    • Это создает зависимость между прошлыми и будущими значениями RAX, поэтому ЦП не может оптимизировать инструкцию, используя переименование регистров.

В отличие от:

MOV RAX, [0x1234]
  • читает 8 байтов памяти, начиная с 0x1234 (обратное перезаписывает 8 байтов памяти)
  • переписывает все RAX
  • Предполагается, что байты в памяти имеют тот же порядок байтов, что и процессор (часто это не так в сетевых пакетах, поэтому мой SHR инструкция лет назад)

Также важно отметить:

MOV EAX, [0x1234]

Тогда, как уже упоминалось в комментариях, есть:

MOVZX EAX, byte [0x1234]
  • читает только один байт памяти в 0x1234
  • расширяет значение, чтобы заполнить все EAX (и, следовательно, RAX) нулями (устраняя зависимость и позволяя оптимизировать переименование регистров).

Во всех этих случаях, если вы хотите записать из регистра "А" в память, вам нужно будет выбрать вашу ширину:

MOV [0x1234], AL   ; write a byte (8 bits)
MOV [0x1234], AX   ; write a word (16 bits)
MOV [0x1234], EAX  ; write a dword (32 bits)
MOV [0x1234], RAX  ; write a qword (64 bits)

Вы задаете несколько вопросов здесь.

Если вы просто загрузите младшие 8 бит регистра, остальная часть регистра сохранит свое предыдущее значение. Это может объяснить, почему ваш системный вызов получил неправильные параметры.

Одна из причин использования 32 бит, когда это все, что вам нужно, заключается в том, что многие инструкции, использующие EAX или EBX, на один байт короче, чем те, которые используют RAX или RBX. Это также может означать, что константы, загруженные в регистр, короче.

Набор инструкций развивался в течение долгого времени и имеет немало причуд!

Если вам просто нужны 32-битные регистры, вы можете безопасно работать с ними, это нормально под 64-битными. Но если вам просто нужны 16-битные или 8-битные регистры, старайтесь избегать их или всегда используйте movzx/movsx для очистки оставшихся битов. Хорошо известно, что в x86-64 использование 32-битных операндов очищает старшие биты 64-битного регистра. Основная цель этого - избежать ложных цепочек зависимостей.

Пожалуйста, обратитесь к соответствующему разделу - 3.4.1.1 - Руководства разработчика программного обеспечения Intel® 64 и IA-32, том 1:

32-битные операнды генерируют 32-битный результат, расширенный от нуля до 64-битного результата в целевом регистре назначения

Разрыв цепочек зависимостей позволяет выполнять инструкции параллельно, в произвольном порядке, с помощью алгоритма Out-of-Order, реализованного внутренне процессорами начиная с Pentium Pro в 1995 году.

Цитата из Справочного руководства по оптимизации архитектур Intel® 64 и IA-32, раздел 3.5.1.8:

Последовательности кода, которые модифицируют частичный регистр, могут испытывать некоторую задержку в своей цепочке зависимостей, но этого можно избежать, используя идиомы нарушения зависимости. В процессорах, основанных на микроархитектуре Intel Core, ряд инструкций может помочь очистить зависимость выполнения, когда программное обеспечение использует эти инструкции для очистки содержимого регистра до нуля. Разбивайте зависимости между частями регистров между инструкциями, используя 32-битные регистры вместо частичных регистров. Для ходов это можно сделать с помощью 32-битных ходов или с помощью MOVZX.

Правило 37 кодирования ассемблера / компилятора (влияние M, универсальность MH): Разрывать зависимости между частями регистров между инструкциями, используя 32-битные регистры вместо частичных регистров. Для ходов это можно сделать с помощью 32-битных ходов или с помощью MOVZX.

MOVZX и MOV с 32-разрядными операндами для x64 эквивалентны - все они разрывают цепочки зависимостей.

Вот почему ваш код будет выполняться быстрее, если вы всегда будете пытаться очистить старшие биты больших регистров при использовании меньших регистров. Когда биты всегда очищены, и нет зависимости от предыдущего значения регистра, ЦП может внутренне переименовать регистры.

Переименование регистров - это метод, используемый внутренне процессором, который устраняет ложные зависимости данных, возникающие из-за повторного использования регистров последовательными инструкциями, между которыми нет реальных зависимостей данных.

Если вы хотите работать только с 8-битным количеством, то вам нужно работать с регистром AL. То же самое для AX и EAX.

Например, у вас может быть 64-битное значение, которое содержит два 32-битных значения. Вы можете работать на младших 32-битных, открыв регистр EAX. Если вы хотите работать с старшими 32-битными значениями, вы можете поменять местами два 32-битных значения (повернуть DWORD в регистре), чтобы старшие биты теперь были в EAX.

64-bit это самый большой фрагмент памяти, с которым вы можете работать как единое целое. Это не значит, что это то, сколько вам нужно использовать.

Если вам нужно 8 бит, используйте 8. Если вам нужно 16, используйте 16. Если не имеет значения, сколько битов, то не имеет значения, сколько вы используете.

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

Другие вопросы по тегам