mov
-среднее дорого для констант
Это может быть очевидным, но я все равно оставлю это здесь. В общем случае стоит задуматься о представлении числа на битовом уровне, когда вам нужно инициализировать значение.
Инициализация eax
с 0
:
b8 00 00 00 00 mov $0x0,%eax
следует сократить (как для производительности, так и для размера кода ) до
31 c0 xor %eax,%eax
Инициализация eax
с -1
:
b8 ff ff ff ff mov $-1,%eax
можно сократить до
31 c0 xor %eax,%eax
48 dec %eax
или
83 c8 ff or $-1,%eax
Или, в более общем случае, любое 8-битное значение с расширенным знаком может быть создано в 3 байта с push -12
(2 байта) / pop %eax
(1 байт). Это даже работает для 64-битных регистров без дополнительного префикса REX; push
/ pop
размер операнда по умолчанию = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Или, учитывая известную константу в регистре, вы можете создать другую соседнюю константу, используя lea 123(%eax), %ecx
(3 байта). Это удобно, если вам нужен нулевой регистр и константа; xor-ноль (2 байта) + lea-disp8
(3 байта).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
См. Также Установите все биты в регистре процессора на 1 эффективно
push 200; pop edx
- 3 байта для инициализации.dec
, например,xor eax, eax; dec eax
push imm8
/pop reg
составляет 3 байта и отлично подходит для 64-битных констант на x86-64, гдеdec
/inc
составляет 2 байта. Иpush r64
/pop 64
(2 байта) может даже заменить 3 байтаmov r64, r64
(3 байта на REX). См. Также Установка всех битов в регистре ЦП на 1 для таких вещей, какlea eax, [rcx-1]
заданное известное значение вeax
(например, если нужен нулевой регистр и другая константа, просто используйте LEA вместо push / popВо многих случаях инструкции на основе аккумулятора (то есть те, которые принимают
(R|E)AX
в качестве операнда назначения) на 1 байт короче, чем инструкции общего случая; увидеть этот вопрос на StackOverflow.источник
al, imm8
особые случаи, такие какor al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ поja .non_alphabetic
2 байта каждый вместо 3. Использованиеal
для символьных данных также позволяетlodsb
и / илиstosb
. Или используйтеal
для проверки чего-либо о младшем байте EAX, например,lodsd
/test al, 1
/setnz cl
делает cl = 1 или 0 для нечетного / четного. Но в редком случае, когда вам нужен 32-битный немедленный, тогда, конечноop eax, imm32
, как в моем ответе хроматический ключВыберите соглашение о вызовах, чтобы поставить аргументы там, где вы хотите.
Язык вашего ответа - asm (на самом деле машинный код), поэтому рассматривайте его как часть программы, написанной на asm, а не на C-compiled-for-x86. Ваша функция не должна легко вызываться из C с любым стандартным соглашением о вызовах. Это хороший бонус, если он не будет стоить вам лишних байтов.
В чистой программе asm некоторые вспомогательные функции обычно используют соглашение о вызовах, которое удобно для них и для их вызывающей стороны. Такие функции документируют свое соглашение о вызовах (входы / выходы / сгустки) с комментариями
В реальной жизни даже программы asm (я думаю), как правило, используют согласованные соглашения о вызовах для большинства функций (особенно для разных исходных файлов), но любая важная функция может делать что-то особенное. В Code-Golf вы оптимизируете дерьмо из одной функции, так что, очевидно, это важно / особенное.
Чтобы протестировать вашу функцию из C-программы, можете написать оболочку, которая помещает аргументы в нужных местах, сохраняет / восстанавливает любые дополнительные регистры, которые вы закрываете, и помещает возвращаемое значение,
e/rax
если его там еще не было.Пределы того, что разумно: все, что не налагает чрезмерное бремя на звонящего:
Требующий DF (флаг направления строки для
lods
/stos
/ и т. Д.) Был очищен (вверх) при вызове / повторении, это нормально. Позволить ему быть неопределенным на call / ret было бы хорошо. Требование очистки или установки при входе, но затем изменение его при возвращении было бы странным.Возвращать значения FP в x87
st0
разумно, но возвращать вst3
с мусором в другом регистре x87 - нет. Звонящий должен будет очистить стек x87. Даже возвращениеst0
с непустыми регистрами старшего стека также будет сомнительным (если только вы не возвращаете несколько значений).call
, так же[rsp]
как и ваш обратный адрес. Вы можете избежатьcall
/ret
на x86, используя регистрацию ссылок вродеlea rbx, [ret_addr]
/jmp function
и вернуться с помощьюjmp rbx
, но это не «разумно». Это не так эффективно, как call / ret, так что это не то, что вы правдоподобно найдете в реальном коде.Пограничные случаи: напишите функцию, которая создает последовательность в массиве, учитывая первые 2 элемента как аргументы функции . Я выбрал, чтобы вызывающая сторона сохраняла начало последовательности в массиве и просто передавала указатель на массив. Это определенно изгибает требования вопроса. Я подумал о том, чтобы взять упакованные аргументы
xmm0
для formovlps [rdi], xmm0
, что также было бы странным соглашением о вызовах.Вернуть логическое значение во FLAGS (коды условий)
Системные вызовы OS X делают это (
CF=0
означает отсутствие ошибок): считается ли плохой практикой использование регистра флагов в качестве логического возвращаемого значения? ,Любое условие, которое можно проверить с помощью одного JCC, вполне разумно, особенно если вы можете выбрать условие, имеющее семантическое отношение к проблеме. (например, функция сравнения может установить флаги так
jne
будут приняты, если они не были равны).Требуются узкие арги (вроде
char
) были знаком или нулем, расширенным до 32 или 64 бит.Это не лишено смысла; использование
movzx
илиmovsx
избежание частичного замедления регистрации является нормальным явлением в современной архитектуре x86. Фактически, clang / LLVM уже создает код, который зависит от недокументированного расширения соглашения о вызовах System V в x86-64: аргументы, которые меньше 32 бит, являются знаком или нулем, расширяемым вызывающей стороной до 32 бит .Вы можете задокументировать / описать расширение до 64 бит, написав
uint64_t
илиint64_t
в своем прототипе, если хотите. Например, вы можете использоватьloop
инструкцию, которая использует все 64 бита RCX, если только вы не используете префикс размера адреса, чтобы переопределить размер до 32-битного ECX (да, действительно, размер адреса не размер операнда).Обратите внимание, что
long
это только 32-битный тип в 64-битном ABI Windows и Linux x32 ABI ;uint64_t
является однозначным и короче, чем типunsigned long long
.Существующие соглашения о вызовах:
Windows 32-битная
__fastcall
, уже предложенная другим ответом : целочисленные аргументы вecx
иedx
.x86-64 System V : передает много аргументов в регистрах и имеет много регистров с закрытыми вызовами, которые вы можете использовать без префиксов REX. Что еще более важно, это было фактически выбрано, чтобы позволить компиляторам встроить
memcpy
или memset так жеrep movsb
легко: первые 6 аргументов целого числа / указателя передаются в RDI, RSI, RDX, RCX, R8, R9.Если ваша функция использует
lodsd
/stosd
внутри цикла, который выполняетсяrcx
раз (сloop
инструкцией), вы можете сказать «вызывается из C, какint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
в соглашении о вызовах System V в x86-64». Пример: рирпроекции .32-битный GCC
regparm
: целочисленные аргументы в EAX , ECX, EDX, возврат в EAX (или EDX: EAX). Наличие первого аргумента в том же регистре, что и возвращаемое значение, позволяет провести некоторые оптимизации, как в этом случае с вызывающим примером и прототипом с атрибутом функции . И, конечно, AL / EAX специально для некоторых инструкций.Linux x32 ABI использует 32-разрядные указатели в длинном режиме, так что вы можете сохранить префикс REX при изменении указателя ( пример использования ). Вы по-прежнему можете использовать 64-битный размер адреса, если только у вас нет 32-битного отрицательного целого, расширенного нулями в регистре (так что это будет большое значение без знака, если вы
[rdi + rdx]
).Обратите внимание, что
push rsp
/pop rax
составляет 2 байта и эквивалентноmov rax,rsp
, так что вы все равно можете копировать полные 64-битные регистры в 2 байта.источник
ret 16
; они не выталкивают адрес возврата, выдвигают массив, затемpush rcx
/ret
. Вызывающая сторона должна знать размер массива или сохранить RSP где-нибудь за пределами стека, чтобы найти себя.Используйте краткие формы для специальных случаев для AL / AX / EAX, а также другие короткие формы и однобайтовые инструкции
Примеры предполагают 32/64-битный режим, где размер операнда по умолчанию составляет 32 бита. Префикс размера операнда меняет инструкцию на AX вместо EAX (или наоборот в 16-битном режиме).
inc/dec
регистр (кроме 8-битного):inc eax
/dec ebp
. (Не x86-64:0x4x
байты кода операции были переназначены как префиксы REX, поэтомуinc r/m32
это единственная кодировка.)8-разрядный
inc bl
2 байта, используяinc r/m8
опкод + ModR / M операнд , кодирующий . Так что используйтеinc ebx
для увеличенияbl
, если это безопасно. (например, если вам не нужен результат ZF в случаях, когда старшие байты могут быть ненулевыми).scasd
:e/rdi+=4
, требует, чтобы регистр указывал на читаемую память. Иногда полезно, даже если вас не волнует результат FLAGS (например,cmp eax,[rdi]
/rdi+=4
). А в 64-битном режимеscasb
может работать как 1 байтinc rdi
, если lodsb или stosb бесполезны.xchg eax, r32
: Это где 0x90 NOP пришли:xchg eax,eax
. Пример: переупорядочить 3 регистра с двумяxchg
инструкциями в циклеcdq
/ для GCD в 8 байтов, где большинство инструкций являются однобайтовыми, включая злоупотребление / вместо /idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
: расширение знака EAX в EDX: EAX, то есть копирование старшего бита EAX во все биты EDX. Чтобы создать ноль с известным неотрицательным, или получить 0 / -1 для добавления / sub или маски с. Урок истории x86:cltq
противmovslq
, а также AT & T против мнемоники Intel для этого и связанных с нимcdqe
.lodsb / d : как
mov eax, [rsi]
/rsi += 4
без заглушающих флагов. (Предполагая, что DF ясен, какие стандартные соглашения о вызовах требуются при входе в функцию.) Также stosb / d, иногда scas и реже movs / cmps.push
/pop reg
. например, в 64-битном режимеpush rsp
/pop rdi
составляет 2 байта, ноmov rdi, rsp
требует префикса REX и составляет 3 байта.xlatb
существует, но редко бывает полезным. Большой справочной таблицы - это то, чего следует избегать. Я также никогда не находил применения для AAA / DAA или других инструкций, упакованных BCD или 2-ASCII-цифрами.1 байт
lahf
/sahf
редко используются. Вы могли быlahf
/and ah, 1
в качестве альтернативыsetc ah
, но это, как правило, бесполезно.А для CF, в частности,
sbb eax,eax
нужно получить 0 / -1 или даже недокументированный, но универсально поддерживаемый 1-байтsalc
(установите AL из Carry), что эффективно неsbb al,al
влияет на флаги. (Удалено в x86-64). Я использовал SALC в конкурсе « Оценка пользователей № 1: Деннис» .1-байт
cmc
/clc
/stc
(flip («дополнение»), очистить или установить CF) редко используются, хотя я нашел применение дляcmc
сложения с расширенной точностью с базовыми 10 ^ 9 кусками. Чтобы безоговорочно установить / очистить CF, обычно организуйте, чтобы это происходило как часть другой инструкции, например,xor eax,eax
очищает CF, а также EAX. Не существует эквивалентных инструкций для других флагов условий, только DF (направление строки) и IF (прерывания). Флаг переноса специально для множества инструкций; сдвиги устанавливают его,adc al, 0
могут добавить его в AL в 2 байта, и я упоминал ранее недокументированный SALC.std
/cld
редко, кажется, стоит . Особенно в 32-битном коде лучше просто использоватьdec
указатель иmov
операнд или источник памяти для инструкции ALU вместо установки DF solodsb
/stosb
go вниз, а не вверх. Обычно, если вам нужен нисходящий поток, у вас все еще есть еще один указатель, поэтому вам нужно больше, чем одинstd
иcld
во всей функции, чтобы использоватьlods
/stos
для обоих. Вместо этого просто используйте строковые инструкции для направления вверх. (Стандартные соглашения о вызовах гарантируют DF = 0 при входе в функцию, поэтому вы можете предположить, что это бесплатно без использованияcld
.)История 8086 года: почему существуют эти кодировки
В оригинальных 8086, AX было очень особенным: инструкции нравятся
lodsb
/stosb
,cbw
,mul
/div
и другие используют его неявно. Это все еще так, конечно; В текущем x86 не пропал ни один из 8080-х операционных кодов (по крайней мере, ни один из официально документированных). Но позже процессоры добавили новые инструкции, которые давали лучшие / более эффективные способы выполнения действий без предварительного копирования или замены их в AX. (Или в EAX в 32-битном режиме.)например, в 8086 отсутствовали более поздние дополнения, такие как
movsx
/movzx
для загрузки или перемещения + знак-удлинение, или 2-х и 3-х операнды,imul cx, bx, 1234
которые не дают результата с половиной и не имеют никаких неявных операндов.Кроме того, основным узким местом 8086 была выборка инструкций, поэтому оптимизация под размер кода была важна для производительности в то время . Дизайнер ISA 8086 (Стивен Морс) потратил много места для кодирования кода операции в особых случаях для AX / AL, включая специальные (E) коды операции AX / AL-destination для всех основных инструкций ALU- непосредственного кода, просто код операции + немедленный без байта ModR / M. 2-байтовый
add/sub/and/or/xor/cmp/test/... AL,imm8
илиAX,imm16
или (в 32-битном режиме)EAX,imm32
.Но для этого нет особого случая
EAX,imm8
, поэтому обычное кодирование ModR / Madd eax,4
короче.Предполагается, что если вы собираетесь работать с некоторыми данными, вы захотите использовать их в AX / AL, поэтому вам, возможно, захочется заменить регистр на AX , возможно, даже чаще, чем копировать регистр в AX с помощью
mov
,Все, что касается кодирования инструкций 8086, поддерживает эту парадигму: от инструкций, подобных
lodsb/w
всем кодировкам для особых случаев, для немедленных с EAX до неявного использования даже для умножения / деления.Не увлекайся; обменять все на EAX не всегда автоматически, особенно если вам нужно использовать немедленные операции с 32-разрядными регистрами вместо 8-разрядных. Или если вам нужно чередовать операции с несколькими переменными в регистрах одновременно. Или, если вы используете инструкции с 2 регистрами, не сразу.
Но всегда имейте в виду: я делаю что-нибудь, что было бы короче в EAX / AL? Могу ли я переставить так, чтобы у меня было это в AL, или я в настоящее время пользуюсь преимуществом AL с тем, для чего я уже его использую.
Свободно смешивайте 8-битные и 32-битные операции, чтобы воспользоваться преимуществами, когда это безопасно (вам не нужно выносить данные в полный регистр или что-то в этом роде).
источник
cdq
это полезно дляdiv
чего нулюedx
во многих случаях.cdq
перед беззнаковыми,div
если знаете, что ваш дивиденд ниже 2 ^ 31 (то есть неотрицательный, когда рассматривается как подписанный), или если вы используете его перед установкойeax
потенциально большого значения. Обычно (вне code-golf) вы бы использовали егоcdq
как настройкуidiv
, так иxor edx,edx
раньшеdiv
Используйте
fastcall
соглашенияПлатформа x86 имеет много соглашений о вызовах . Вы должны использовать те, которые передают параметры в регистрах. На x86_64 первые несколько параметров в любом случае передаются в регистрах, так что проблем нет. На 32-битных платформах соглашение о вызовах по умолчанию (
cdecl
) передает параметры в стек, что не годится для игры в гольф - для доступа к параметрам в стеке требуются длинные инструкции.При использовании
fastcall
на 32-битных платформах 2 первых параметра обычно передаются вecx
иedx
. Если ваша функция имеет 3 параметра, вы можете рассмотреть возможность ее реализации на 64-битной платформе.Прототипы функций C для
fastcall
соглашения (взяты из этого примера ответа ):источник
Вычтите -128 вместо добавления 128
Точно так же, добавить -128 вместо вычитать 128
источник
< 128
в<= 127
уменьшить величину немедленного операндаcmp
, или GCC всегда предпочитает переставляя сравнивает, чтобы уменьшить величину, даже если это не -129 против -128.Создайте 3 нуля с помощью
mul
(затемinc
/,dec
чтобы получить +1 / -1, а также ноль)Вы можете обнулить eax и edx, умножив на ноль в третьем регистре.
в результате EAX, EDX и EBX будут равны нулю всего за четыре байта. Вы можете обнулить EAX и EDX в трех байтах:
Но с этой начальной точки вы не можете получить 3-й регистр с нулем в еще одном байте или регистр +1 или -1 в еще 2 байта. Вместо этого используйте технику мул.
Пример использования: объединение чисел Фибоначчи в двоичном виде .
Обратите внимание, что после
LOOP
завершения цикла ECX будет нулевым и может использоваться для обнуления EDX и EAX; Вы не всегда должны создавать первый ноль сxor
.источник
Регистры и флаги процессора находятся в известных состояниях запуска
Можно предположить, что процессор находится в известном и задокументированном состоянии по умолчанию в зависимости от платформы и ОС.
Например:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
источник
_start
. Так что да, это справедливо, если вы пишете программу вместо функции. Я сделал это в Экстрим Фибоначчи . (В динамически выполняемом файле, ld.so бежит перед прыжком к вашему_start
, и делает отпуск мусор в регистрах, а статический только ваш код.)Чтобы сложить или вычесть 1, используйте один байт
inc
илиdec
инструкции, которые меньше, чем многобайтовые инструкции сложения и подчинения.источник
inc/dec r32
с номером регистра, закодированным в коде операции. Таким образом,inc ebx
это 1 байт, ноinc bl
равен 2. Еще меньше, чем,add bl, 1
конечно, для регистров, кромеal
. Также обратите внимание, чтоinc
/dec
оставьте CF без изменений, но обновите другие флаги.lea
для математикиЭто, наверное, одна из первых вещей, которые мы узнаем о x86, но я оставляю это здесь как напоминание.
lea
может использоваться для умножения на 2, 3, 4, 5, 8 или 9 и добавления смещения.Например, для вычисления
ebx = 9*eax + 3
в одной инструкции (в 32-битном режиме):Вот это без смещения:
Вот Это Да! Конечно,
lea
можно использовать и математику какebx = edx + 8*eax + 3
для расчета индексации массива.источник
lea eax, [rcx + 13]
это версия без дополнительных префиксов для 64-битного режима. 32-битный размер операнда (для результата) и 64-битный размер адреса (для входов).Инструкции цикла и строки меньше, чем альтернативные последовательности команд. Наиболее полезным является то,
loop <label>
что меньше, чем две последовательности командdec ECX
иjnz <label>
, иlodsb
меньше, чемmov al,[esi]
иinc si
.источник
mov
маленький сразу в нижние регистры, когда это применимоЕсли вы уже знаете, что верхние биты регистра равны 0, вы можете использовать более короткую инструкцию для немедленного перемещения в нижние регистры.
против
Используйте
push
/pop
для imm8, чтобы обнулить старшие битыБлагодарю Питера Кордеса.
xor
/mov
4 байта, ноpush
/pop
только 3!источник
mov al, 0xa
хорошо, если вам не нужно, чтобы он был расширен до нуля. Но если вы это сделаете, xor / mov будет 4 байта против 3 для push imm8 / pop илиlea
другой известной константы. Это может быть полезно в сочетании сmul
обнулением 3 регистров в 4 байта илиcdq
, если вам нужно много констант.[0x80..0xFF]
, которые не могут быть представлены как расширенный знак imm8. Или, если вы уже знаете старшие байты, например,mov cl, 0x10
послеloop
инструкции, потому что единственный способloop
не перейти - это когда она выполненаrcx=0
. (Я думаю, вы сказали это, но ваш пример используетxor
). Вы даже можете использовать младший байт регистра для чего-то другого, пока что-то еще возвращает его к нулю (или как угодно), когда вы закончите. Например, моя программа Фибоначчи хранится-1024
в ebx и использует bl.xchg eax, r32
), например,mov bl, 10
/dec bl
/jnz
чтобы ваш код не заботился о старших байтах RBX.В ФЛАГИ устанавливаются после многих инструкций
После многих арифметических инструкций флаг переноса (без знака) и флаг переполнения (со знаком) устанавливаются автоматически ( дополнительная информация ). Флаг знака и флаг нуля устанавливаются после многих арифметических и логических операций. Это можно использовать для условного ветвления.
Пример:
ZF устанавливается этой инструкцией, поэтому мы можем использовать ее для условного ветвления.
источник
test al,1
; Вы обычно не получаете это бесплатно. (Илиand al,1
создать целое число 0/1 в зависимости от нечетного / четного.)test
/cmp
», то это будет довольно простой для новичка x86, но все же стоит воздержаться.Используйте циклы do-while вместо циклов while
Это не специфично для x86, но широко применимо для начинающих. Если вы знаете, что цикл while будет запускаться хотя бы один раз, переписывание цикла как цикла do-while с проверкой состояния цикла в конце часто сохраняет 2-байтовую инструкцию перехода. В особом случае вы можете даже использовать
loop
.источник
do{}while()
при сборке используется естественная цикличность (особенно для эффективности). Также обратите внимание, что 2-байтовыйjecxz
/jrcxz
перед циклом работает очень хорошо,loop
чтобы обрабатывать регистр «необходимо запустить нулевое время» «эффективно» (на редких процессорах, гдеloop
не медленно).jecxz
также можно использовать внутри цикла для реализацииwhile(ecx){}
, сjmp
нижней.Используйте любые удобные соглашения о вызовах
System V x86 использует стек и System V x86-64 использует
rdi
,rsi
,rdx
,rcx
и т.д. для входных параметров, а также вrax
качестве возвращаемого значения, но это вполне разумно использовать свое собственное соглашение о вызовах. __fastcall используетecx
и вedx
качестве входных параметров, а другие компиляторы / ОС используют свои собственные соглашения . Используйте стек и все, что записывается как ввод / вывод, когда это удобно.Пример: счетчик повторяющихся байтов , использующий умное соглашение о вызовах для 1-байтового решения.
Meta: запись ввода в регистры , запись вывода в регистры
Другие ресурсы: заметки Агнера Фога о соглашениях о вызовах
источник
int 0x80
что требуется куча настроек.int 0x80
в 32-битном коде илиsyscall
в 64-битном кодеsys_write
- единственный хороший способ. Это то, что я использовал для Extreme Fibonacci . В 64-битном коде__NR_write = 1 = STDOUT_FILENO
, так что вы можетеmov eax, edi
. Или, если старшие байты EAX равны нулю,mov al, 4
в 32-битном коде. Вы также можетеcall printf
илиputs
, я думаю, написать ответ «x86 asm для Linux + glibc». Я думаю, что не стоит считать пространство ввода PLT или GOT или сам код библиотеки.char*buf
строку в ней с ручным форматированием. например, как это (неловко оптимизировано для скорости) asm FizzBuzz , где я получил строковые данные в регистр, а затем сохранил их сmov
, потому что строки были короткими и фиксированной длины.Используйте условные ходы
CMOVcc
и наборыSETcc
Это скорее напоминание для меня, но инструкции по условному набору существуют и инструкции по условному перемещению существуют на процессорах P6 (Pentium Pro) или новее. Существует много инструкций, основанных на одном или нескольких флагах, установленных в EFLAGS.
источник
cmov
имеет 2-байтовый код операции (0F 4x +ModR/M
), так что это минимум 3 байта. Но источником является r / m32, поэтому вы можете условно загрузить его в 3 байта. Помимо ветвления,setcc
полезно в большем количестве случаев, чемcmovcc
. Тем не менее, рассмотрим весь набор инструкций, а не только базовые 386 инструкций. (Хотя инструкции SSE2 и BMI / BMI2 настолько велики, что они редко бывают полезными. Их длинаrorx eax, ecx, 32
составляет 6 байт, они длиннее, чем mov + ror. Отличная производительность, а не игра в гольф, если только POPCNT или PDEP не спасут много иснов)setcc
.Экономьте на
jmp
байтах, упорядочивая if / then, а не if / then / elseЭто, конечно, очень просто, просто подумал, что я опубликую это как то, о чем нужно подумать, играя в гольф. В качестве примера рассмотрим следующий простой код для декодирования шестнадцатеричного символа:
Это может быть сокращено на два байта, позволяя падежу «then» попасть в регистр «else»:
источник
sub
задержка на критическом пути для одного случая не является частью цепочки зависимостей, переносимых циклом (как здесь, где каждая входная цифра независима до слияния 4-битных блоков ). Но я думаю, +1 в любом случае. Кстати, в вашем примере есть отдельная пропущенная оптимизация: если вам всеmovzx
равно понадобится конец, то используйтеsub $imm, %al
not EAX, чтобы воспользоваться 2-байтовым кодированием no-modrmop $imm, %al
.cmp
, делаяsub $'A'-10, %al
;jae .was_alpha
;add $('A'-10)-'0'
, (Я думаю, что я понял логику правильно). Обратите внимание, что'A'-10 > '9'
так нет никакой двусмысленности. Вычитая исправление для буквы, мы обернем десятичную цифру. Так что это безопасно, если мы предполагаем, что наши входные данные являются действительными шестнадцатеричными, как и ваши.Вы можете извлекать последовательные объекты из стека, задав для esi значение esp и выполнив последовательность lodsd / xchg reg, eax.
источник
pop eax
/pop edx
/ ...? Если вам нужно оставить их в стеке, вы можетеpush
вернуть их обратно для восстановления ESP, по-прежнему 2 байта на объект без необходимостиmov esi,esp
. Или вы имели в виду для 4-байтовых объектов в 64-битном коде, гдеpop
бы получить 8 байт? Кстати, вы даже можете использоватьpop
для зацикливания буфера с более высокой производительностью, чемlodsd
, например, для сложения с повышенной точностью в Extreme FibonacciДля Codegolf и ASM: используйте инструкции, используйте только регистры, нажмите всплывающее окно, минимизируйте память регистров или память немедленно
источник
Чтобы скопировать 64-битный регистр, используйте
push rcx
;pop rdx
вместо 3-х байтmov
.Размер операнда по умолчанию для push / pop - 64-битный без префикса REX.
(Префикс размера операнда может переопределить размер push / pop до 16-битного, но 32-битный размер операнда / pop не может быть закодирован в 64-битном режиме даже если REX.W = 0.)
Если один или оба регистра являются
r8
..r15
, используйтеmov
потому что для push и / или pop потребуется префикс REX. В худшем случае это на самом деле проигрывает, если обоим нужны префиксы REX. Очевидно, что вы все равно должны избегать r8..r15 в кодовом гольфе.Во время разработки с этим макросом NASM вы можете сделать свой источник более читабельным . Просто помните, что он идет на 8 байтов ниже RSP. (В красной зоне в x86-64 System V). Но в нормальных условиях это замена для 64-битной
mov r64,r64
илиmov r64, -128..127
Примеры:
xchg
Часть примера потому , что иногда вам нужно получить значение в EAX или RAX и не заботятся о сохранении старой копии. Однако push / pop не помогает вам обмениваться.источник