Как сдвинуть влево в сборке с ADD, не используя SHL? - PullRequest
3 голосов
/ 27 сентября 2019

Наш учитель в классе дал нам задание делать.Проблема в том, что мы не обсуждали сдвиг в классе, поэтому я немного растерялся, как это сделать.В инструкциях по назначению он говорит нам следующее:

  • Мы можем использовать только инструкции mov, add, sub для выполнения задания.
  • Он дает нам следующую подсказку: Подумайте, какдобавить инструкции могут быть использованы для достижения сдвига.Например, предположим, что у нас есть следующее 8-битное двоичное число: 00000011, после смещения этого двоичного числа влево мы имеем.00001100.

Мы начинаем с 4 переменных:

  • var1 BYTE 41h
  • var2 BYTE 42h
  • var3 BYTE 43h
  • var4 BYTE 44h

Первая часть задания заключается в их перемещении, поэтому мы получаем следующий порядок:

  • var1 = 44h
  • var2 = 41h
  • var3 = 42h
  • var4 = 43h

Я успешно завершил эту часть, я только предоставил ее для контекста.Следующая часть - это то, где у меня возникают проблемы.Во второй части мы должны переместить эти переменные в регистр eax.var1 должен храниться в старшем байте eax, var2 во втором старшем, war3 во втором младшем байте и var4 в младшем.

Конечный результат должен давать eax = 444144243

Таквот что я знаю об этой проблеме:

  • Я знаю, что не могу напрямую обратиться к старшим 16 битам eax.
  • Я могу сослаться на топор и др.
  • Я знаю, что мне нужно использовать двоичные значения этих шестнадцатеричных значений, чтобы сместить их влево, добавив 1 или что-то в этом роде.

Как мне сместить var1 так, чтобы онзаканчивается в 16 старших битах eax и т. д. с другими переменными?

ПРИМЕЧАНИЕ. У меня нет кода, так как я не знаю, с чего начать эту часть задания.Я не хочу, чтобы это было решено, я просто хочу помочь найти правильный путь, чтобы решить это.

Спасибо.

Ответы [ 2 ]

2 голосов
/ 27 сентября 2019

Чтобы сдвинуть значение влево, вам нужно ДОБАВИТЬ то же значение к себе

Например, если у вас есть 0011

0011 + 0011 = 0110 (shift 1 left)
0110 + 0110 = 1100 (shift 1 left again)

Чтобы решить вашу проблему, я бы сделал следующее(быстрый способ)

MOV ah, var1  (move 44h to 0000h -> 4400h)
MOV al, var2  (move 41h to 4400h -> 4441h)
ADD eax, eax    (4441h + 4441h = 8882h)
ADD eax, eax    (8882h + 8882h = 11104h)
ADD eax, eax    (11104h + 11104h = 22208h)
ADD eax, eax    (22208h + 22208h = 44410h)
ADD eax, eax    
ADD eax, eax    
ADD eax, eax   
ADD eax, eax    (444100h)
ADD eax, eax  
ADD eax, eax  
ADD eax, eax 
ADD eax, eax    (4441000h)
ADD eax, eax   
ADD eax, eax   
ADD eax, eax   
ADD eax, eax    (44410000h)

Теперь перейдем к другой части

MOV ah, var3  (move 42h to 44410000h -> 44414200h)
MOV al, var4  (move 43h to 44414200h -> 44414243h)

Теперь ваш eax-регистр выглядит следующим образом: 4441 4243h

0 голосов
/ 28 сентября 2019

Я знаю, что мне нужно использовать двоичные значения этих шестнадцатеричных значений, чтобы сдвинуть их влево, добавив 1 или что-то в этом роде.

Шестнадцатеричный - это читаемый человеком способ печатидвоичные значения.Ваши байты в регистрах и памяти просто состоят из битов, потому что компьютеры используют двоичную логику.

Поскольку вы не можете использовать команды сдвига или поворота (кроме сдвига влево на 1 с помощью add same,same), я бырассмотрите возможность использования сохранения / перезагрузки для временного получения байтов в нужном порядке.

Обратите внимание, что желаемый результат изменяет порядок данных только с байтовой гранулярностью, то есть перемещается вокруг целых байтов.Никакие биты не должны перемещаться между байтами. Так что вам не обязательно нужно сдвигать биты.

Ваша идея не ошибается, но она требует много инструкций.Я просто представляю другие способы.

Суть упражнения, кажется, учит вас тому, как псевдонимы AX, AH и AL относятся к частям EAX.(И аналогично для ECX, EDX и EBX).И / или как загрузка / сохранение меча влияет на 4 отдельных байта памяти, к которым он обращается, то есть порядок байтов с прямым порядком байтов.

Я не вижу смысла запрещать shl eax,8 или 16, еслиПредполагаемое решение - использовать add 16 раз в качестве смены.Все еще существует проблема использования частичных регистров для изменения байтов EAX.Я имею в виду, что использование идентификатора x+x = 2*x = x<<1 часто полезно, например, выполнение математических операций в режимах адресации с LEA, таких как x*3 = lea eax, [eax + eax*2].Или используя add same,same как более эффективный shl eax,1.


Я успешно завершил эту часть, я просто предоставил это для контекста.Следующая часть - это то, где у меня возникают проблемы.Во второй части мы должны переместить эти переменные в регистр EAX.var1 должен храниться в старшем байте eax [...]

Предполагается, что переменные хранятся в указанном порядке (сначала var1, следовательно, по наименьшему адресу: * 1034).*

Порядок, который вы указали, равен напротив того, что вы получите от 4-байтовой нагрузки, которая покрывает все 4 байта: x86 имеет младший порядок байтов, поэтому байт с самым низким адресом заканчивается вмладший байт EAX (он же AL).

Обычно вы выполняете загрузку dword, а затем bswap eax, чтобы изменить порядок 4 байтов в регистре (или на более новых процессорах, которые его поддерживают, a movbe load: mov данные с прямым порядком байтов обычно, по крайней мере, так же эффективны, как отдельные load + bswap.) Но упражнение заставляет вас стать более изумительным.

Более широкая загрузка / хранение, охватывающее несколькобайтовые переменные

Предполагая, что ваши 4 переменные объявляются непрерывно, сначала var1 (по самому низкому адресу) вы загружаете несколько байтов одновременно словом или словом mov, например

mov   eax,  dword ptr [var1]       ; AL=var1, AH=var2, higher bytes = var3, var4

MASM связывает размер с метками данных, поэтому чтобы он был доволен, мыo применить переопределение размера dword ptr к операнду памяти, чтобы он соответствовал размеру двойного слова в регистре EAX.mov всегда требует, чтобы оба операнда имели одинаковый размер, но для случая, подобного mov [esp], eax, операнд источника регистра EAX подразумевает размер для места назначения памяти.Без метки (или 2-х регистров) вероятность несоответствия отсутствует.Также необязательно, когда размер совпадает сам по себе, например, для mov al, [var1]

Что касается процессора, то все это всего лишь байты;Переопределение dword ptr является чисто исходной вещью, чтобы ассемблер был доволен.Другим ассемблерам, таким как NASM, этого не потребуется.И это только потому, что режим адресации включает метку данных, которую MASM считает «переменной» с размером, связанным с ней.


Учитывая, что вы уже выполнили part1 и сохранили это в памяти, так что вы44h, 41h, 42h, 43h в памяти в таком порядке (т. е. 43'42'41'44h в качестве меча с прямым порядком байтов) вы можете перейти к части 2 примерно так: :

    mov  ax, word ptr [var1]     ; AX = 41'44h = AH:AL
    mov  dx, word ptr [var1 + 2] ; DX = 43'42h = DH:DL

    sub  esp, 4                  ; reserve stack space
    mov  [esp+0], DH
    mov  [esp+1], DL
    mov  [esp+2], AH             ; 4x byte stores
    mov  [esp+3], AL             ; constructing a byte-reversed copy

    mov   eax, [esp]             ; reload that
    add   esp, 4                 ; release the stack allocation

В моих комментариях яразделяли байты с ' при записи 32-битного шестнадцатеричного представления значения EAX, чтобы было легче видеть границы байтов по сравнению с 44414243h

Это гораздо меньше инструкций, предложенных ответом @ Pablo.У него есть недостаток производительности, вызвавший остановку магазина.(Перезагрузка EAX - это данные, которые были только что записаны в 4 отдельных хранилища байтов).Но вместо 16 add инструкций, это, вероятно, все еще лучшая задержка, а также пропускная способность!Задержка пересылки магазина составляет «всего» около 15 циклов общей задержки хранения -> перезагрузки и не блокирует другие хранилища и загрузки в течение этого времени.См. Еще один абзац ниже.

Вариант с 4-х байтовой загрузкой и двумя word хранилищами был бы вариантом, но это потребовало бы частичного слияния регистров и, вероятно, было бы хуже для Intel.См. Реализацию части 1 ниже, которая делает это, ближе к концу этого ответа.


Эффективное выполнение первой части

Если бы вы могли использовать любые нужные вам инструкции , вы бы просто использовали левое вращение, чтобы переместить старшие 8 бит вниз к началу, а остальные биты оставили влево, чтобы освободить место.

                                     ;             MSB(var4)    LSB(var1)
                                     ; dword at var1 = 44'43'42'41h
    rol   dword ptr [var1], 8        ; dword at var1 = 43'42'41'44h

Или вы можете использовать сдвиг влево на 1байт (8 бит):

    mov  eax, dword ptr [var1]        ; EAX = 44'43'42'41h
    add  eax, eax                     ; shift left by 8 bits, 1 at a time
    add  eax, eax
    add  eax, eax
    add  eax, eax
    add  eax, eax
    add  eax, eax
    add  eax, eax
    add  eax, eax                     ; EAX = 43'42'41'00h  ; after shifting

    mov  al, [var4]                   ; EAX = 43'42'41'44h  ; replace the low byte
    mov  dword ptr [var1], eax        ; overwrite var1..4 with the bytes of EAX

Запись AL, а затем чтение EAX может привести к частичной остановке регистра на некоторых старых процессорах, но нам не нужно отдельно писать AH, поэтому современные процессоры не будут иметьлюбая проблема с производительностью здесь.

Было бы очень удобно, если бы мы могли просто сделать хранилище DWORD, которое записывало бы за пределами 4 байтов, то есть наступить на гипотетический var5.Тогда мы могли бы просто сделать

;; writes 1 byte past var4
    mov  eax, dword ptr [var1]
    mov  dl,  [var4]

    mov dword ptr [var2], eax         ; overwrite var2..4 and 1 extra byte past the end
    mov  [var1], dl

Мы можем использовать некоторое временное хранилище в стеке, чтобы сделать эту работу, а затем скопировать обратно в var1, но это немного неуклюже:

   sub  esp, 8              ; reserve some stack space
   mov  eax, dword ptr [var1]        ; EAX = 44'43'42'41h

   mov  [esp], eax
   mov  [esp+4], eax                 ; 2 copies of EAX so we can take any 4-byte window

   mov  eax, [esp+3]                 ; EAX = 43'42'41'44h = rotated
   mov  dword ptr [var1], eax        ; and store that over var1..4
   add  esp, 8              ; and dealloc it.

Это эмулирует вращение, объединяя 2 копии последовательности байтов в памяти (в двух словах).Тогда мы можем загрузить любое 4-байтовое окно.Мы выбираем тот, где младший байт берется из старшего байта исходного значения, 1-байтный (8-битный) левый поворот = 3-байтный (24-битный) правый поворот.

Это приведет ккиоск экспедиции магазина, к сожалению.(Два хранилища слов читаются загрузкой, которая перекрывает их оба).Так что около ~ 15 циклов задержки вместо обычных ~ 5 на современном x86.(https://agner.org/optimize).

Это может быть лучше для пропускной способности, чем версия с 8x add, но хуже для задержки. Или, может быть, хуже для пропускной способности, так как независимые цепочки add могут перекрывать их выполнение. Но такможет независимо сохранять / перезагружать даже при остановках пересылки в хранилище.


При загрузке только байтов:

Загрузка всего перед выполнением каких-либо хранилищ означает, что нам не нужно беспокоиться об уничтожении данных, которые мыеще не читал.

    mov  ah, [var1]       ; EAX = ??'??'41'??h
    mov  al, [var4]       ; EAX = ??'??'41'44h
    mov  ch, [var3]       ; ECX = ??'??'43'??h
    mov  cl, [var2]       ; ECX = ??'??'43'42h

    mov  word ptr [var3], cx  ;  var3 = CL = 42h   var4 = CH = 43h
    mov  word ptr [var1], ax  ;  var1 = AL = 44h   var2 = AH = 41h

Другой очевидный вариант - две word загрузки и 4 byte хранилищ. Для производительности, это позволило бы избежать частичных слияний регистров слияния (от записи AL / AH изатем чтение AX) на процессорах Intel, но пропускная способность загрузки обычно лучше, чем пропускная способность магазина. Хотя штраф за слияние AH может быть хуже, чем у Haswell / Skylake

Но так как мы хотим сохранить 2 исходных байтапо порядку мы можем объединить нагрузки CL и CH в одну нагрузку CX:

Пожалуй, самая эффективная версия части 1

    mov  ah, [var1]              ; EAX = ??'??'41'??h
    mov  al, [var4]              ; EAX = ??'??'41'44h
    mov  cx, word ptr [var2]     ; ECX = ??'??'43'42h

    mov  word ptr [var3], cx  ;  var3 = CL = 42h   var4 = CH = 43h
    mov  word ptr [var1], ax  ;  var1 = AL = 44h   var2 = AH = 41h

Примечаниечто мы выполняем загрузку слов из var2, затем из хранилища слов в var3, сдвигая эти 2 байта влево на 8 бит / 1 байт.

Единственное задержка здесь - чтение AX после записи AL +AH, и это самое большее цикл во внешнем интерфейсе (во время которого объединяющийся уоп выдает сам себя) в семействе Intel Sandybridge.

Если более поздний код выполнит загрузку dword из 4 байтов, которые мы переставилиочень скоро после этого, его запись в 2 половинки слова приведет к киоску пересылки магазина.Но моя эмуляция bswap выше перезагружается только в две половины word, так что все в порядке.

Это также имеет ложную зависимость от старого значения EAX и ECX (чего можно избежать для ECX, выполняя загрузку dword, но все же хранилище слов).Либо для EAX mov eax,0 или sub eax,eax обнулит его и (на большинстве процессоров) нарушит зависимости от старого значения, что позволит выполнить эту последовательность инструкций вне очереди, даже если последний блок, который использовал ECX для другогозначение по-прежнему останавливается.

Это меньше инструкций (и каждая инструкция все еще достаточно эффективна), чем версия 8x add или версия, использующая пустое пространство в стеке.И не сохраняет / перезагружает во временное хранилище.


Объединение части 1 и части 2:

Окончание с требуемыми данными в EAX и обратным байтом в памятиможет быть объединен в один шаг, возможно, повторное использование временного в стеке.

Конечно, если вы ограничены в выборе команд, вы просто сделаете:

    mov   eax, dword ptr [var1]
    ror   eax, 8
    mov   dword ptr [var1], eax
    bswap eax

Оптимизация части 1 и2 вместе только с mov, а add / sub оставлено в качестве упражнения для читателя.

Другие забавные идеи: обмен XOR также работает с SUB, так что вы можете неэффективно поменять местами байт между регистром и памятью.Но вы не ограничены в использовании регистров tmp, поэтому лучше загрузить их в отдельный регистр байтов.

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