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