Как преобразовать логическое выражение в код сборки - PullRequest
1 голос
/ 17 апреля 2020

Я довольно новичок в сборке
Рассмотрим следующую функцию:
enter image description here

, где '+' обозначает 'Or' logi c gate и объединение переменных, означает «И» logi c gate.
Как я могу реализовать такую ​​функцию в emu8086? учитывая, что входные параметры могут обозначать биты регистра AL, например, выходные данные будут менять свое значение на 0 или 1.

ОБНОВЛЕНИЕ: вот что я сделал, я знаю, что это плохо написано, и это действительно работает, если есть предложение или более простой способ, дайте мне знать
спасибо всем за вашу помощь, особенно Питер.

org 100h

mov al, 0A3h     pour le test              

mov ah, al
and ah, 01h        ;ah = x0 
shr al, 1

mov bl, al 
and bl, 01h        ;bl = x1 
shr al, 1

mov bh, al
and bh, 01h            
not bh
and bh, 01h         ;bh = !x2
shr al, 1

mov cl, al 
and cl, 01h        
not cl
and cl, 01h        ;cl = !x3
shr al, 1

mov ch, al
and ch, 01h        
not ch
and ch, 01h        ;ch = !x4
shr al, 1

mov dl, al
and dl, 01h        ;x5 = dl
shr al, 1

mov dh, al
and dh, 01h        
not dh
and dh, 01h        ;dh = !x6    

shr al, 1          ;al = x7  


and bh, dl
and bh, cl
and bh, ah         ;!x2 and x5 and !x3 and x0     

and dh, bl 
and dh, ch
and dh, al         ;!x6 and x1 and !x4 and x7 

or dh, bh   

mov ah, dh         ;resultat dans ah







ret

Ответы [ 2 ]

2 голосов
/ 18 апреля 2020

Вы уверены , что выражение с четырьмя 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 будет хорошим провалом быстрого пути.

1 голос
/ 19 апреля 2020

Вдохновленный комментариями Питера Кордеса и prl :

int test(char x)
{
    return ((x & 0x2d) == 0x21) || ((x & 0xd2) == 0x82);
}

Godbolt (x86 msv c v19.24 / Os) сгенерировано:

   _x$ = 8                                       ; size = 1
int test(char) PROC                                  ; test
        mov     cl, BYTE PTR _x$[esp-4]
        mov     al, cl
        and     al, 45                                    ; 0000002dH
        cmp     al, 33                                    ; 00000021H
        je      SHORT $LN3@test
        and     cl, 210                             ; 000000d2H
        cmp     cl, 130                             ; 00000082H
        je      SHORT $LN3@test
        xor     eax, eax
        ret     0
$LN3@test:
        mov     eax, 1
        ret     0
int test(char) ENDP                                  ; test
...