Ответы:
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 эффективно
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
для for movlps [rdi], xmm0
, что также было бы странным соглашением о вызовах.
Системные вызовы 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 so lodsb
/ stosb
go вниз, а не вверх. Обычно, если вам нужен нисходящий поток, у вас все еще есть еще один указатель, поэтому вам нужно больше, чем один std
и cld
во всей функции, чтобы использовать lods
/ stos
для обоих. Вместо этого просто используйте строковые инструкции для направления вверх. (Стандартные соглашения о вызовах гарантируют DF = 0 при входе в функцию, поэтому вы можете предположить, что это бесплатно без использования cld
.)
В оригинальных 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 / M add 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
соглашения (взяты из этого примера ответа ):
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU
0100 81C38000 ADD BX,0080
0104 83EB80 SUB BX,-80
Точно так же, добавить -128 вместо вычитать 128
< 128
в <= 127
уменьшить величину немедленного операнда cmp
, или GCC всегда предпочитает переставляя сравнивает, чтобы уменьшить величину, даже если это не -129 против -128.
mul
(затем inc
/, dec
чтобы получить +1 / -1, а также ноль)Вы можете обнулить eax и edx, умножив на ноль в третьем регистре.
xor ebx, ebx ; 2B ebx = 0
mul ebx ; 2B eax=edx = 0
inc ebx ; 1B ebx=1
в результате EAX, EDX и EBX будут равны нулю всего за четыре байта. Вы можете обнулить EAX и EDX в трех байтах:
xor eax, eax
cdq
Но с этой начальной точки вы не можете получить 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-битном режиме):
8d 5c c0 03 lea 0x3(%eax,%eax,8),%ebx
Вот это без смещения:
8d 1c c0 lea (%eax,%eax,8),%ebx
Вот Это Да! Конечно, 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, вы можете использовать более короткую инструкцию для немедленного перемещения в нижние регистры.
b8 0a 00 00 00 mov $0xa,%eax
против
b0 0a mov $0xa,%al
push
/ pop
для imm8, чтобы обнулить старшие битыБлагодарю Питера Кордеса. xor
/ mov
4 байта, но push
/ pop
только 3!
6a 0a push $0xa
58 pop %eax
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.
После многих арифметических инструкций флаг переноса (без знака) и флаг переполнения (со знаком) устанавливаются автоматически ( дополнительная информация ). Флаг знака и флаг нуля устанавливаются после многих арифметических и логических операций. Это можно использовать для условного ветвления.
Пример:
d1 f8 sar %eax
ZF устанавливается этой инструкцией, поэтому мы можем использовать ее для условного ветвления.
test al,1
; Вы обычно не получаете это бесплатно. (Или and al,1
создать целое число 0/1 в зависимости от нечетного / четного.)
test
/ cmp
», то это будет довольно простой для новичка x86, но все же стоит воздержаться.
Это не специфично для 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Это, конечно, очень просто, просто подумал, что я опубликую это как то, о чем нужно подумать, играя в гольф. В качестве примера рассмотрим следующий простой код для декодирования шестнадцатеричного символа:
cmp $'A', %al
jae .Lletter
sub $'0', %al
jmp .Lprocess
.Lletter:
sub $('A'-10), %al
.Lprocess:
movzbl %al, %eax
...
Это может быть сокращено на два байта, позволяя падежу «then» попасть в регистр «else»:
cmp $'A', %al
jb .digit
sub $('A'-'0'-10), %eax
.digit:
sub $'0', %eax
movzbl %al, %eax
...
sub
задержка на критическом пути для одного случая не является частью цепочки зависимостей, переносимых циклом (как здесь, где каждая входная цифра независима до слияния 4-битных блоков ). Но я думаю, +1 в любом случае. Кстати, в вашем примере есть отдельная пропущенная оптимизация: если вам все movzx
равно понадобится конец, то используйте sub $imm, %al
not EAX, чтобы воспользоваться 2-байтовым кодированием no-modrm op $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
Чтобы скопировать 64-битный регистр, используйте push rcx
; pop rdx
вместо 3-х байт mov
.
Размер операнда по умолчанию для push / pop - 64-битный без префикса REX.
51 push rcx
5a pop rdx
vs.
48 89 ca mov rdx,rcx
(Префикс размера операнда может переопределить размер 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
; mov %1, %2 ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
%macro MOVE 2
push %2
pop %1
%endmacro
Примеры:
MOVE rax, rsi ; 2 bytes (push + pop)
MOVE rbp, rdx ; 2 bytes (push + pop)
mov ecx, edi ; 2 bytes. 32-bit operand size doesn't need REX prefixes
MOVE r8, r10 ; 4 bytes, don't use
mov r8, r10 ; 3 bytes, REX prefix has W=1 and the bits for reg and r/m being high
xchg eax, edi ; 1 byte (special xchg-with-accumulator opcodes)
xchg rax, rdi ; 2 bytes (REX.W + that)
xchg ecx, edx ; 2 bytes (normal xchg + modrm)
xchg rcx, rdx ; 3 bytes (normal REX + xchg + modrm)
xchg
Часть примера потому , что иногда вам нужно получить значение в EAX или RAX и не заботятся о сохранении старой копии. Однако push / pop не помогает вам обмениваться.
push 200; pop edx
- 3 байта для инициализации.