Сборка x86: извлечь значение без его сохранения

Можно ли в сборке x86 удалить значение из стека без его сохранения? Что-то вроде pop word null? Я, очевидно, мог бы использовать add esp,4, но, может быть, мне не хватает красивой и чистой мнемоники cisc?


person NeoTheThird    schedule 09.02.2018    source источник
comment
Это будет add esp,4 для 32-битной версии и add rsp,8 для 64-битной.   -  person Rudy Velthuis    schedule 09.02.2018
comment
@RudyVelthuis К сожалению, вы правы, спасибо за подсказку.   -  person NeoTheThird    schedule 09.02.2018
comment


Ответы (1)


add esp,4 / add rsp,8 это нормальный/идиоматический/чистый способ. Никакого специального способа не требуется, потому что стеки не являются волшебными или особенными (по крайней мере, в этом отношении); это просто указатель в регистре с некоторыми инструкциями, которые используют его неявно. (А для стеков ядра прерывания используют его асинхронно, поэтому программное обеспечение не может реализовать красную зону ядра, даже если бы захотело...)

Помимо этого, волшебный способ CISC для очистки всего кадра стека в конце функции — это leave = mov esp, ebp / pop ebp (или 16- или 64-битный эквивалент). В отличие от enter, он достаточно быстр на современных процессорах, чтобы его можно было использовать на практике, но все еще является инструкцией 3 uop для процессоров Intel. (http://agner.org/optimize/). Но leave работает только в том случае, если вы потратили дополнительные инструкции на создание кадра стека с ebp / rbp в первую очередь. (Обычно вы бы этого не сделали, если только вам не нужно зарезервировать переменный объем пространства стека, например, с push в цикле для создания массива или эквивалентом C99 VLA или alloca. Или для начального кода, чтобы получить доступ к locals проще или в 16-битном режиме, где SP нельзя использовать в режимах адресации.)

Волшебный способ CISC для очистки аргументов стека заключается в том, чтобы вызываемый объект использовал ret imm16. (стоимостью 1 дополнительная моп) для извлечения аргументов, создавая соглашение о вызовах, при котором вызываемый объект очищает стек. В соглашении о вызовах вызывающего абонента нет возможности использовать эту форму ret, но вы можете просто оставить смещение стека и использовать mov для хранения аргументов для следующего вызова функции вместо push (если функции нужны какие-либо аргументы стека в all; соглашения о вызовах register-arg обычно более эффективны.)

Таким образом, волшебные способы CISC не имеют преимущества в производительности на современных процессорах, только небольшой размер кода.


Есть 2 причины, по которым вы можете использовать pop reg вместо add esp,4:

  • code-size: pop r32/r64 — это однобайтовая инструкция, а не 3 байта для add esp,4 или 4 байта для add rsp,8.
  • производительность: механизм стека Intel должен вставлять дополнительные операции синхронизации стека, когда вы используете esp / rsp явно после инструкции стека (push/pop/call/ret). Таким образом, после call (которое возвращается с ret) он сохраняет uop для использования pop вместо add esp,4 до того, как вы ret в конце функции.

    Механизм стека AMD не нуждается в дополнительных операциях синхронизации стека, но по-прежнему выполняет однократные инструкции push/pop. В отличие от старых процессоров Intel/AMD, где push/pop стоит больше, чем простая mov загрузка/сохранение, требуется отдельная uop для модификации указателя стека. И создание зависимости данных от указателя стека.

См. Почему помещает ли эта функция RAX в стек в качестве первой операции? подробнее о производительности.

Если вы стремились к эстетике, вы можете сделать отступ, отформатировать и красиво прокомментировать свой код, но помимо вы выбрали неправильный язык, когда выбрали x86 asm, если эстетика перевешивает оптимизацию.


Конечно, если вам нужно настроить стек более чем на 1 ширину регистра, обязательно используйте add, если вам не нужны данные, которые загрузит pop. Или, если вам нужно изменить его на +128 байт, используйте sub esp, -128, потому что -128 кодируется как расширенный знак imm8, а +128 — нет.

Или, может быть, использовать lea esp, [esp+4], как это делает gcc с -mtune=atom. (Для упорядоченного атома, а не для сильвермонта). Как я уже сказал, если вы хотели чистоты, вам не следовало выбирать x86 asm.


Вы почти всегда можете найти мертвый регистр, чтобы pop в него. Если вам нужно настроить E/RSP на один слот стека перед извлечением некоторых регистров, которые вы действительно хотели извлечь, вы всегда можете извлечь один и тот же регистр дважды.

В крайне редких случаях, когда ни один из 7 (x86-32) или 15 (x86-64) нестековых регистров недоступен в качестве назначения pop, эта оптимизация недоступна, и вам следует просто использовать традиционный add.< /strong> Не стоит тратить дополнительные инструкции, чтобы сделать возможным pop; это перевешивало бы незначительное преимущество использования pop.

Обратите внимание, что pop Sreg (сегментный регистр) по-прежнему использует обычную «ширину стека» (32 или 64 бита, в зависимости от режима), а не только 16 для 16-битного регистра. Но только pop ds/es/ss являются однобайтовыми. pop fs/gs — по 2 байта каждый. Поэтому, если вы оптимизируете размер кода, pop gs будет на 1 байт меньше, чем add esp,4, но намного медленнее. (Или на 2 байта меньше, чем add rsp,8).

person Peter Cordes    schedule 09.02.2018
comment
Вы можете захотеть написать стильную версию asm на первой итерации, прежде чем проводить бенчмаркинг и выбивать из нее все дерьмо, чтобы у вас была хотя бы достаточно чистая базовая версия. Но, конечно, если вам не нужны производительность или размер, вам следует держаться подальше от asm, поэтому мой первоначальный комментарий по вопросу находится на грани неправильности, я, вероятно, удалю его, теперь, когда вы ответили на него намного лучше. - person Ped7g; 09.02.2018
comment
@Ped7g: Только что понял, что leave и ret imm16 можно квалифицировать как волшебный CISC. - person Peter Cordes; 09.02.2018