С инструкциями с 2 операндами в стиле x86, которые уничтожают их назначение, вы всегда можете смоделировать неразрушающую инструкцию с 3 операндами с помощью mov
, чтобы скопировать один операнд в назначение , а затем выполнить деструктивную инструкцию в этом пункте назначения.
# with ecx and edx holding your inputs (which I'm calling C and D).
mov ebx, ecx ; ebx = C
sub ebx, edx ; ebx = C - D
Это оптимально для этого случая, когда вам не нужно уничтожать значения в ECX и EDX.
Если у вас мало доступных регистров, хорошим вариантом может быть сохранение ECX в стеке, а затем получение результата C - D
в ECX вместо нового регистра.
Часто вы можете продолжать использовать один и тот же регистр для одной и той же переменной во всей функции, но это не обязательно, а иногда и не оптимально. Используйте комментарии, чтобы отслеживать вещи.
Компиляторы, как правило, довольно хороши в распределении регистров, но их код может быть трудно читаемым, потому что они даже не пытаются согласоваться с использованием регистров. Для неразрушающих операций они часто помещают результат в новый регистр без причины. Тем не менее, вывод компилятора часто является хорошей отправной точкой для оптимизации. (Напишите крошечную функцию, которая что-то делает, и посмотрите, как она компилируется. Или напишите всю свою вещь в C с аргументами функций вместо констант в качестве входных данных и скомпилируйте ее.)
x86 имеет некоторые инструкции по копированию и работе для других операций (не sub
), , в первую очередь LEA .
lea ebx, [ecx + ecx*4] ; ebx = C * 5
lea ebx, [ecx + ebx - 2] ; ebx = C + D - 2
Режимы адресации x86 могут добавлять или вычитать константы, но могут только сдвигать влево и добавлять регистры.
Форма с непосредственным операндом imul
также является 3-операндом для использования с множителями, которые нельзя сделать с 1 или 2 LEA:
imul ebx, ecx, 0x01010101 ; ebx = C repeated 4 times, if it was < 256
В отличие от большинства инструкций с непосредственным операндом, imul
не перегружает поле /r
в байте ModRM как дополнительные биты кода операции. Таким образом, у него есть место для кодирования места назначения регистра и источника reg / mem, потому что 286 выделил ему целый байт кода операции.
ISA-расширения , такие как BMI1 и BMI2, добавили некоторые новые трехзначные целочисленные инструкции, такие как ANDN и SHRX .
andn ebx, ecx, edx ; ebx = (~C) & D ; BMI1
shrx ebx, edx, ecx ; ebx = D >> C ; BMI2
Но они не доступны повсеместно, только Haswell и позже, и Ryzen. (А версии Haswell / Skylake для Pentium / Celeron по-прежнему продаются без них, что еще больше отодвигает точку, в которой они становятся базовыми. Спасибо, Intel.)
И, конечно же, для векторных инструкций AVX предоставляет неразрушающие версии всех инструкций SSE .
movaps xmm2, xmm0 ; copy a whole register
subsd xmm2, xmm1 ; scalar double-precision FP subtract: xmm0-xmm1
vsubsd xmm3, xmm0, xmm1
или менее очевидный вариант использования
xorps xmm0, xmm0 ; zero the register and break any false dependencies
cvtsi2sd xmm0, eax ; convert to double-precision FP, with the upper element = 0
xorps xmm1, xmm1
cvtsi2sd xmm1, edx
против. AVX:
vxorps xmm1, xmm1,xmm1 ; xmm1 = all-zero
vcvtsi2sd xmm0, xmm1, eax
vcvtsi2sd xmm1, xmm1, edx
При этом используется тот же обнуленный регистр, что и в месте назначения слияния, чтобы избежать ложных зависимостей (и иметь верхний 64-битный ноль 128-битного регистра).