Ваш asm не имеет никакого смысла (push esp
копирует в память, а не в другой регистр), а mul edi
пишет EDX: EAX не edi
. Это делает EDX:EAX = EAX * src_operand
. Прочтите руководство: https://www.felixcloutier.com/x86/MUL.html. Или, лучше, используйте imul
вместо этого, если вам не нужно вывод с половиной старшего из 32x32 => 64-битного умножения.
Кроме того, не используйте регистр указателя стека ESP для хранения временных значений, если вы точно не знаете, что делаете (например, находитесь в пространстве пользователя, и вы убедились, что никакие обработчики сигналов не могут асинхронно использовать стек). Stack-pointer * 4 + large-constant is не что-то, что обычная программа могла бы когда-либо делать.
Обычно вы могли бы сделать это в одной инструкции LEA , но ESP - единственный регистр, который не может быть индексом в режиме адресации x86. См. rbp не разрешен в качестве базы SIB? (Индекс является частью режима адресации, к которому может применяться 2-битный счетчик сдвига, он же масштабный коэффициент).
Я думаю наша лучшая ставка по-прежнему просто копировать ESP в EDI, а затем использовать LEA:
mov edi, esp
lea edi, [edi * 4 + 0x11223344]
Или вы можете копировать и добавлять с LEA, и затем сдвиг влево, потому что добавляемое нами значение имеет два ноля в качестве младших битов (т.е. это кратное 4). Таким образом, мы можем сдвинуть его вправо на 2 без потери битов.
SHIFTED_ADD_CONSTANT equ 0x11223344 >> 2
lea edi, [esp + SHIFTED_ADD_CONSTANT]
shl edi, 2
Добавление перед сдвигом влево приведет к переносу в верхние 2 бита, но мы собираемся сдвинуть эти биты, чтобы Неважно, что там.
Это также 2 мопа, и больше эффективно на процессорах семейства AMD Bulldozer (нет mov-elission для GP-integer mov
и где масштабируется Индекс стоит дополнительный цикл задержки для LEA). У Zen есть mov-elission, но я думаю, что все еще одни и те же задержки LEA, поэтому обе версии имеют задержку в 2 цикла. Даже «сложный» LEA имеет 2 / тактовую пропускную способность на Zen или 4 / тактовую частоту для простого LEA (любой порт ALU).
Но меньше , эффективный на Intel IvyBridge и более поздних процессорах, где mov
может работать с нулевой задержкой (исключение mov), а режим адресации [edi*4 + disp32]
по-прежнему является быстрым 2-компонентным LEA. Таким образом, для процессоров Intel с mov-elmination, первая версия - это 2 входных интерфейса, 1 неиспользуемый домен для исполняющего модуля и только 1 цикл задержки.
Другой вариант с 2 инструкциями - использовать медленнее imul
вместо быстрого сдвига. (В режимах адресации используется сдвиг: даже если он записан как * 1 / 2 / 4 / 8
, в машинном коде он закодирован в 2-битном поле счетчика сдвига).
imul edi, esp, 4 ; this is dumb, don't use mul/imul for powers of 2.
add edi, 0x11223344
imul
имеет современную задержку в 3 цикла Процессоры x86, которые довольно хороши, но медленнее на старых процессорах, таких как Pentium 3. Все еще не так хорошо, как задержка в 1 или 2 цикла для mov + LEA, и imul
работает на меньшем количестве портов.
(Количество инструкций обычно не нужно оптимизировать; обычно число операций увеличивается, а также задержка / пропускная способность. Также размер кода в байтах машинного кода x86; разные инструкции имеют разную длину.)