В x86-64 всегда ли мы выполняем pushq, когда хотим поместить sh что-то в стек? - PullRequest
0 голосов
/ 08 мая 2020

Поскольку все 16 регистров могут быть 8 байтами в x86-64, в начале вызова функции, когда функция (вызываемая) должна сделать pu sh, вызываемые сохраненные регистры (% rbx,% rbp и% r12-15 ), который он хочет использовать, у него нет возможности узнать, сохранил ли вызывающий абонент 64-битные или 32-битные, 16-битные или 8-битные значения в этих регистрах, поэтому им всегда нужно вызывать pushq в pu sh все 8 байтов этих регистров в стек, а не pushl? Другими словами, используются ли pushl и pushw в x86-64?

1 Ответ

2 голосов
/ 08 мая 2020

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

Всегда эффективно читать полный регистр после того, как 32-битная младшая половина была записана, потому что 32-битный регистр записывает неявно с расширением нуля до 64-битного . Чтение 64-битного регистра после того, как вызывающая сторона записала младшие 8 или 16 битов, может вызвать остановку частичного регистра в микроархитектурах семейства Intel P6, если вызывающий не позаботился о том, как он использовал регистр перед созданием call. На современных компьютерах (не Intel P6) регистр размера 8/16-битного операнда записывает уже оплаченный , какой бы штраф за слияние мог существовать (обычно ложная зависимость) . (Я замалчиваю пару деталей, таких как частичное переименование AH, которое все еще используется в современных Intel, включая Skylake)


Хотя вы могли переместить указатель стека с помощью sub $24, %rsp и используйте movl или movb для хранения 32-битных или 8-битных младших частей некоторых регистров, это безопасно, только если вы знаете что-то о том, как ваш вызывающий абонент использует регистры, и хотите соответствующим образом оптимизировать. (Делает вашу функцию зависимой от внутренних компонентов вызывающего, а не только от ABI). Даже если бы это был вариант для некоторой вспомогательной функции, обычно не стоило бы уменьшать размер вашего кадра стека на несколько байтов.

(Функции, использующие 16-битные данные, редко, но 8-битные данные не редкость. bool и char являются общими. Компиляторы обычно используют movzx или movzbl загрузки из память с нулевым расширением до полных регистров и часто может использовать 32-битный размер операнда, чтобы избежать фактических махинаций с частичными регистрами.Но им было все равно, если вы сохранили / восстановили только младшие 8 бит с помощью mov store / movzbl reload , для регистров, в которых компиляция хранит расширенные нулями bool или char.)

Использовались ли pushl и pushw когда-либо в x86-64?

pushl буквально не существует в 64-битном режиме ; 32-битный размер операнда для push равен , не кодируется даже с префиксом REX.W=0 .

pushw кодируется, но никогда не используется компиляторами в 32-битном или 64-битном режиме. (И, как правило, бесполезно и не рекомендуется для людей, за исключением странных угловых случаев или хаков, таких как, возможно, шеллкод. Я использовал его один раз, когда кодировал (оптимизируя размер кода), объединяя два 16-битных значения в один регистр для Adler -32 ).

Если бы компилятор действительно хотел хранить слова или двойные слова (например, в неоптимизированных сборках для вывода аргументов входящих регистров), он просто использовал бы movw или movl.

Обычно вы хотите, чтобы стек был выровнен по 16 , чтобы вы были готовы сделать еще один вызов функции; вот почему я предложил sub $24, %rsp выше. (При вводе функции RSP указывает на адрес возврата, который отправил ваш вызывающий. RSP + 8 и RSP-8 выровнены по 16 байт.)


pushq %reg очень эффективен на современных ЦП: декодирует в один uop на процессорах с механизмом стека (который обрабатывает обновления RSP) вне серверной части OoO exe c. Это настолько эффективно, что clang использует push %rax или другой фиктивный регистр вместо sub $8, %rsp, когда ему нужно только переместить указатель стека на 8 байтов, например, чтобы перестроить стек перед другим вызовом.

pushq %reg - это 1-байтовая инструкция (или 2 байта для r8..r15, включая префикс REX)

...