Відповіді:
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-comlated-for-x86. Ваша функція не повинна легко телефонувати з C за допомогою будь-якого стандартного режиму виклику. Це приємний бонус, якщо він не коштує вам зайвих байтів.
У чистій програмі asm для деяких допоміжних функцій нормально використовувати умовний виклик, який є зручним для них та для їх абонента. Такі функції документують свою угоду про виклики (входи / виходи / клобери) з коментарями.
У реальному житті навіть програми asm (я думаю) схильні використовувати послідовні умови викликів для більшості функцій (особливо для різних вихідних файлів), але будь-яка важлива функція може зробити щось особливе. У коді-гольф ви оптимізуєте лайно з однієї єдиної функції, тому очевидно, що це важливо / особливо.
Щоб перевірити свою функцію за допомогою програми C, ви можете написати обгортку, яка розміщує аргументи в потрібних місцях, зберігає / відновлює будь-які зайві регістри, які ви клобуть, і встановлює повернене значення, e/rax
якщо його ще не було.
Потрібно, щоб DF (прапор напряму рядка для lods
/ stos
/ тощо) був чітким (вгору) під час виклику / повернення нормально. Не дозволяти це визначитись під час виклику / повернення, було б нормально. Вимагаючи, щоб він був очищений або встановлений під час вступу, але потім залишати його модифікованим, коли ви повернетесь, було б дивним.
Повернення значень FP в x87 st0
є розумним, але повернення st3
зі сміттям в інший регістр x87 не є. Абонент повинен був би очистити стек x87. Навіть повернення в st0
не порожні регістри вищих стеків також буде сумнівним (якщо ви не повертаєте кілька значень).
call
, [rsp]
як і ваша зворотна адреса. Ви можете уникнути call
/ ret
на x86, використовуючи регістр посилань, як lea rbx, [ret_addr]
/ jmp function
і повертатися з jmp rbx
, але це не "розумно". Це не настільки ефективно, як call / ret, тому ви не правдоподібно знайдете в реальному коді.Прикордонні випадки: запишіть функцію, яка виробляє послідовність у масиві, задавши перші 2 елементи як аргументи функції . Я вирішив, щоб абонент зберігав початок послідовності в масив і просто передав вказівник на масив. Це, безумовно, зводить вимоги до питання. Я подумав взяти аргументи, запаковані xmm0
для movlps [rdi], xmm0
, що також було б дивною умовою закликання.
Системні виклики OS X роблять це ( CF=0
означає відсутність помилок): Чи вважається поганою практикою використання регістра прапорів як булевого повернення? .
Будь-яка умова, яку можна перевірити за допомогою одного СКК, цілком розумна, особливо якщо ви можете обрати той, який має якесь семантичне значення для проблеми. (наприклад, функція порівняння може встановити прапори, тому jne
вони будуть прийняті, якщо вони не були рівними).
char
) були знаковими або нульовими, розширеними до 32 або 64 біт.Це нерозумно; використання movzx
або movsx
уникнення часткового реєстру сповільнення нормально в сучасних x86 asm. Насправді clang / LLVM вже робить код, який залежить від незадокументованого розширення до системи x86-64 System V виклику: аргументи, вужчі за 32 біти, є знаковими або нульовими, дозволеними абонентом до 32 біт .
Ви можете документувати / описати розширення до 64 біт, написавши uint64_t
або int64_t
в своєму прототипі, якщо хочете. наприклад, ви можете використовувати loop
інструкцію, яка використовує цілі 64 біти RCX, якщо ви не використовуєте префікс розміру адреси для зміни розміру до 32-бітного ECX (так, дійсно, розмір адреси не розмір операнду).
Зауважте, що long
в 64-розрядному ABI для Windows та 32 x біт Linux x32 є лише 32-розрядний тип ; uint64_t
є однозначним і коротшим, ніж тип unsigned long long
.
32-бітний Windows __fastcall
, вже запропонований іншою відповіддю : цілі аргументи в ecx
і edx
.
x86-64 Система V : передає безліч аргументів у регістри та має безліч регістрів з клобуванням викликів, які можна використовувати без префіксів REX. Що ще важливіше, він був фактично обраний, щоб дозволити компіляторам memcpy
вбудовувати чи запам'ятовувати так само 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)
і в системі V86 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
кодування opcode + 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
/ idiv
циклі для GCD у 8 байт, де більшість інструкцій є однобайтовими, включаючи зловживання inc ecx
/ loop
замість test ecx,ecx
/jnz
cdq
: знак-розширення EAX в EDX: EAX, тобто копіювання високого біта EAX на всі біти EDX. Щоб створити нуль з відомими негативними або отримати 0 / -1 для додавання / суб або маски за допомогою. x86 урок історії: cltq
vs.movslq
, а також мнемоніка AT&T vs. 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: Dennis ♦ .
1-байтовий cmc
/ clc
/ stc
(фліп ("доповнення"), очищення або встановлення 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 так lodsb
/ stosb
спуститися вниз, а не вгору. Зазвичай, якщо вам взагалі потрібно вниз, у вас все ще є інший вказівник, що піднімається вгору, тому для використання / для обох вам знадобиться більше одного std
і cld
всієї функції . Замість цього просто використовуйте строкові інструкції для напрямку вгору. (Стандартні умови виклику гарантують DF = 0 при введенні функції, тому ви можете вважати це безкоштовно, не використовуючи .)lods
stos
cld
В оригінальних 8086, AX було дуже особливим: інструкції подобаються lodsb
/ stosb
, cbw
, mul
/ div
і інші використовують його неявно. Це все одно так; поточний x86 не скинув жодного з 8086 опкодів (принаймні, жодного з офіційно задокументованих). Але пізніше процесори додали нові вказівки, які давали кращі / ефективніші способи робити речі, не скопіюючи їх і не замінюючи їх в AX спочатку. (Або в EAX в 32-бітному режимі.)
наприклад, у 8086 бракувало пізніших доповнень, таких як movsx
/ movzx
для завантаження або переміщення + розширення знаків, або 2 та 3-операнди imul cx, bx, 1234
, які не дають результату з високою половиною та не мають явних операндів.
Крім того, основним вузьким місцем 8086 було отримання інструкцій, тому оптимізація розміру коду була важливою для тогочасної продуктивності . Дизайнер ISA 8086 (Стівен Морз) витратив чимало простору кодування коду на спеціальні випадки для AX / AL, включаючи спеціальні (E) AX / AL призначення призначення для всіх основних 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, чи я зараз краще переважаю, якщо я вже використовую його.
Вільно перемішуйте 8-бітні та 32-бітні операції, щоб скористатися ними, коли це безпечно робити (вам не потрібно проводити їх до повного реєстру чи будь-чого іншого).
cdq
є корисним, для div
якого потреби нульові edx
у багатьох випадках.
cdq
раніше, ніж без підписання, div
якщо знаєте, що ваш дивіденд нижче 2 ^ 31 (тобто невід'ємний, якщо трактуватись як підписаний), або якщо ви використовуєте його перед встановленням eax
потенційно великого значення. Зазвичай (за межами коду-гольфу), який ви використовувалиcdq
idiv
xor edx,edx
div
fastcall
конвенціїПлатформа x86 має багато умовних вимог . Ви повинні використовувати ті, які передають параметри в регістри. На x86_64 перші кілька параметрів все-таки передаються в регістри, так що проблем там немає. На 32-бітних платформах звичайний режим виклику за замовчуванням ( cdecl
) передає параметри в стеці, що не годиться для гольфу - доступ до параметрів на стеці вимагає довгих інструкцій.
При використанні fastcall
на 32-бітних платформах два та перші параметри зазвичай передаються в 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
Але з цієї відправної точки ви не можете отримати третій нульовий регістр в одному ще байті або реєстр +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
для імунітету8 до нуля верхніх бітівЗаслуга Петру Кордесу. 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]
, які не є представницькими як ознака, розширена на імунітет8. Або якщо ви вже знаєте верхні байти, наприклад, 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, але широко застосовується підказка для збірки початківців. Якщо ви знаєте, що цикл у той час як цикл буде працювати хоча б один раз, переписуючи цикл як цикл виконання часу, з перевіркою стану циклу в кінці, часто зберігається інструкція про перехід на 2 байти. У окремому випадку ви можете навіть використовувати loop
.
do{}while()
природна циклічна ідіома в зборах (особливо для ефективності). Зауважте також, що 2-байт jecxz
/ jrcxz
перед циклом дуже добре працює з тим, loop
щоб "ефективно" запустити нуль разів "(на рідкісних процесорах, де loop
не повільно). jecxz
також можна використовувати всередині циклу для реалізації awhile(ecx){}
, jmp
внизу.
System V x86 використовує стек і System V x86-64 використовує rdi
, rsi
, rdx
, rcx
і т.д. для вхідних параметрів, а також в rax
якості значення, що повертається, але це цілком розумно використовувати своє власне угоду про виклики. __fastcall використовує ecx
і в edx
якості вхідних параметрів, а також інші компілятори / операційки використовувати свої власні угоди . Використовуйте стек і будь-які регістри, коли це зручно.
Приклад: Повторний байтовий лічильник , використовуючи розумний режим виклику для 1-байтового рішення.
Мета: Введення входів до регістрів , Написання виводу до регістрів
Інші ресурси: Примітки Агнера Фога щодо конвенцій про виклики
int 0x80
що потрібно купувати налаштування.
int 0x80
32-бітний код або syscall
64-бітний код sys_write
- це єдиний хороший спосіб. Це те, що я використовував для Extreme Fibach . У 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
...
Це можна скоротити на два байти, дозволивши випадку "тоді" потрапити у "інший" випадок:
cmp $'A', %al
jb .digit
sub $('A'-'0'-10), %eax
.digit:
sub $'0', %eax
movzbl %al, %eax
...
sub
затримка на критичному шляху для одного випадку не є частиною ланцюга залежностей, що переносяться циклом (наприклад, де кожна вхідна цифра є незалежною до об'єднання 4-бітових фрагментів ). Але я гадаю +1 у будь-якому випадку. До речі, у вашому прикладі є окрема пропущена оптимізація: якщо вам все movzx
одно знадобиться кінець, тоді використовуйте sub $imm, %al
не EAX, щоб скористатися 2-байтним кодуванням no-modrm op $imm, %al
.
cmp
, виконавши sub $'A'-10, %al
; jae .was_alpha
; add $('A'-10)-'0'
. (Я думаю, я правильно зрозумів логіку). Зауважте, що 'A'-10 > '9'
тому двозначності немає. Віднімаючи виправлення на букву, буде зафіксовано десяткову цифру. Отже, це безпечно, якщо ми припускаємо, що наше введення є правильним шістнадцятковим, як і ваш.
Ви можете отримати послідовні об'єкти зі стека, встановивши esi на esp та виконавши послідовність reg-lodsd / xchg, eax.
pop eax
/ pop edx
/ ...? Якщо вам потрібно залишити їх у стеці, ви можете push
повернути їх назад, щоб відновити ESP, все-таки 2 байти на об'єкт без потреби mov esi,esp
. Або ви мали на увазі для 4-байтних об'єктів у 64-бітовому коді, де pop
було б 8 байт? До речі, ви навіть можете використовувати pop
петлю над буфером з кращими характеристиками, ніж lodsd
, наприклад, додавання
Щоб скопіювати 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-розрядний push / pop розмір операнду не кодується в 64-бітному режимі навіть при REX.W = 0.)
Якщо один або обидва регістри є r8
.. r15
, використовуйте, mov
тому що для push і / або pop потрібен префікс REX. Найгірший випадок, що насправді втрачається, якщо обом потрібні префікси REX. Очевидно, що зазвичай у коді гольфу слід уникати r8..r15.
Ви можете зберігати своє джерело легше для читання, розробляючи цей макрос NASM . Просто пам’ятайте, що він крокує на 8 байт нижче RSP. (У червоній зоні в системі x86-64 Система 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 байти для ініціалізації.