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

Я понимаю, что в сборке 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 ]

6 голосов
/ 06 июля 2011

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

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

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

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

3 голосов
/ 16 мая 2017

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

Пожалуйста, обратитесь к соответствующему разделу - 3.4.1.1 - Руководства разработчика ПО Intel® 64 и IA-32 Architectures Том 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 эквивалентны - все они разрывают цепочки зависимостей.

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

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

2 голосов
/ 05 июля 2011

В первую очередь это происходит при загрузке меньшего (например, 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
  • предполагает, что байты в памяти имеют одинаковый endianness как процессор (часто не соответствует действительности в сетевых пакетах, поэтому моя SHR инструкция лет назад)

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

MOV EAX, [0x1234]

Затем, как упоминалось в комментариях, есть:

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

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

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)
1 голос
/ 05 июля 2011

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

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

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

1 голос
/ 05 июля 2011

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

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

...