Как скопировать регистр и сделать `x * 4 + constant` с минимальным количеством инструкций - PullRequest
0 голосов
/ 16 марта 2020

Я новичок в сборке x86. Например, для следующей инструкции: умножьте содержимое ESP на 4 и добавьте 0x11233344, сохранив результат в EDI.

Как мне представить эту инструкцию в сборке x86 с минимальным количеством инструкций?

push esp
mov edi, 4
mul edi
add edi, 0x11233344

1 Ответ

2 голосов
/ 16 марта 2020

Ваш 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; разные инструкции имеют разную длину.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...