Сохраняется по вызову весь регистр , а не только младшее двойное слово или слово. Обычные функции всегда сохраняют / восстанавливают весь регистр 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)