Вы уверены , что выражение с четырьмя x_n
значениями рядом друг с другом должно быть побитовым И, а не объединять их в 4-битные значения? А потом бинарное добавление? Потому что я мог бы догадаться об этом. Если это так, см. https://codegolf.stackexchange.com/a/203610 для сдвига и rcl reg, 1
способ разделения битов между парой регистров. Или на современном x86 с BMI2 вы можете использовать 2x pext
и add
для этого.
Тот факт, что выражения имеют биты в каждой группе в определенном порядке c это не просто возрастание или убывание , вероятно, является подсказкой, что они хотят, чтобы вы распаковали байт в два 4-битных целых числа и сделали с ним обычный +
.
Если мы предположим, что ваш asm является примером правильной функции
Остальная часть этого ответа посвящена оптимизации операций в вашем asm, которые выполняют две группы AND и OR, которые в результате объединяются в одно логическое значение, производя 0
или 1
в AL.
В упрощенную c / прямую реализацию можно внести некоторые улучшения, которые просто извлекают каждый бит отдельно. Например, вам не нужно AND до и после вас NOT. Первое И оставит старшие биты все 0, затем НЕ сделает их равными 1, затем второе И снова сделает их равными нулю.
mov bh, al
; and bh, 01h ; This is pointless
not bh
and bh, 01h ;bh = !x2
Вы можете пойти дальше: вы просто используете битовые операции и заботиться только о младшем бите в каждом регистре. Вы можете and al, 1
один раз в конце, чтобы выделить нужный бит, со всеми временными дисками, несущими мусор в своих старших битах.
Чтобы перевернуть некоторые бит, но не все, используйте XOR с постоянной маской. например, чтобы перевернуть биты 6,4,3,2 в AL и оставить остальные без изменений, используйте xor al, 01011100b
1 . Затем вы можете сдвигать и перемещать в отдельные регистры без необходимости каких-либо инструкций NOT.
Сноска 1: Трейлинг b
обозначает основание 2 / двоичное. Это работает в MASM синтаксисе , IDK, если emu8086 поддерживает его или если вам нужно написать эквивалентный шестнадцатеричный код.
И вы можете И прямо в эти регистры вместо того, чтобы извлекать сначала Таким образом, вам нужно только два чистых регистра.
xor al, 01011100b ; complement bits 6,4,3,2
mov cl, al ; x0, first bit of the 2&5&3&0 group
shr al, 1
mov dl, al ; x1, first bit of the 6&1&4&7 group
shr al, 1
and cl, al ; AND X2 into the first group, X2 & x0
shr al, 1
and cl, al ; cl = X2 & X3 & x0
... ; cl = 2&5&3&0, dl = 6&1&4 with a few more steps
shr al, 1 ; AL = x7
and al, dl ; AL = x6 & x1 & x4 & x7 (reading 6,1,4 from dl)
or al, cl ; logical + apparently is regular (not exclusive) OR
and al, 1 ; clear high garbage
ret
(В простых комментариях ASCII я проигнорировал часть «дополнение», потому что мы обрабатываем все это одной инструкцией в начале).
Насколько я понимаю, мы идем с простой реализацией, которая просто получает биты в конец регистра и выполняет каждую логическую операцию (кроме дополнения) с отдельной инструкцией asm .
Чтобы добиться большего, нам нужно воспользоваться 8 (или 16) битами в регистре, который мы можем сделать параллельно с одной инструкцией. Мы не можем легко перетасовать биты, чтобы они совпали друг с другом, потому что шаблон неправильный.
IDK, если есть что-то умное, мы можем сделать AX со смещением влево, чтобы получить биты из AL в нижнюю часть AH, а также группировка некоторых в верхней части AL. Хм, может быть чередовать shl ax
с rol al
, чтобы отправить биты обратно в нижнюю часть AL. Но это все еще требует 7 смен, чтобы отделить биты. (shl ax,2
и rol al,2
для смежных битов, которые go вместе (7,6 и 3,2) доступны только на 186, а подсчет в CL едва стоит).
Более вероятным углом атаки являются флаги: большинство операций ALU обновляют флаги в соответствии с результатом, причем ZF устанавливается в 1, если все биты в результате равны 0, в противном случае - в 1. Это дает нам горизонтальную операцию ИЛИ для битов в одном регистре. , Поскольку !(a | b)
= !a & !b
, мы можем инвертировать невыполненные биты на входе, чтобы использовать их в качестве горизонтального И вместо ИЛИ. (Я использую !
для однобитного инвертирования. В C, !
- это логическое не, которое превращает любое ненулевое число в 0, в отличие от ~
побитового НЕ.)
Но, к сожалению, у 8086 нет простого способа превратить ZF в 0/1 в регистре напрямую. (386 добавляет setcc r/m8
, например, setz dl
устанавливает DL = 0 или 1 в соответствии с ZF.) Что является возможным для CF. Мы можем получить CF в соответствии с ненулевым регистром, используя sub reg, 1
, который устанавливает CF, если reg был 0 (потому что заем выходит на первое место). В противном случае это очищает CF. Мы можем получить 0 / -1 в регистре согласно CF с sbb al, al
(вычесть с заимствованием). Все части отменяются, оставляя 0 - CF
.
. Чтобы настроить использование флагов, мы можем использовать маски AND, чтобы разделить биты на две группы.
;; UNTESTED, I might have some logic inverted.
xor al, 10100011b ; all bits are the inverse of their state in the original expression.
mov dl, al
and dl, 11010010b ; ~x[7,6,4,1]
and al, 00101101b ; ~x[5,3,2,0]
cmp dl, 1 ; set CF if that group was all zero (i.e. if the original AND was 1), else clear
sbb dl, dl ; dl = -1 or 0 for the first group
cmp al, 1
sbb al, al ; al = -1 or 0 for the second group. Fun fact: undocumented SALC does this
or al, dl ; The + in the original expression
and al, 1 ; keep only the low bit
ret
Возможно, есть даже больше мы можем сделать, например and al, dl
, чтобы очистить биты в AL или нет, в соответствии с результатом SBB в DL. Или, может быть, adc al, -1
вместо cmp al, 1
, чтобы использовать результат CF из DL, чтобы повлиять на то, как CF устанавливается из AL.
Вместо вычитания 1
, вы могли бы sub dl, 11010010b
с использованной маской AND , так что вы получите 0
, если они все были установлены, в противном случае он оборачивается и вы устанавливаете CF. Не уверен, что это полезно.
Количество отрицаний / инверсий быстро становится сложным в вашей голове, но если каждый байт размера кода или каждый цикл производительности имеет значение, то это то, что вы должны изучить. (В наши дни это случается редко, и когда это часто, вы часто векторизуетесь с SSE2 или AVX, чтобы у вас не было флагов, только поразрядно внутри векторных элементов и упакованного сравнения, которое превращает совпадение во все и не совпадение в 0.)
Обратите внимание, что после разбиения с помощью mov / AND ни AL, ни DL не могут быть едиными, поэтому добавление 1
никогда не обернется в ноль. Может быть, sbb al, -1
может добавить 0 или 1 и установить ZF?
Если вы хотите ветвиться, ветвление на ZF хорошо с jz
или jnz
. Это может быть даже лучше на 8086, например, если первая группа AND дает 1
нам не нужно изолировать другую группу. Таким образом, xor al, ...
для дополнения битов соответственно, тогда test al, mask1
/ jnz check_other_group
/ mov al,1
будет хорошим провалом быстрого пути.