Відповіді:
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 урок історії: cltqvs.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 при введенні функції, тому ви можете вважати це безкоштовно, не використовуючи .)lodsstoscld
В оригінальних 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потенційно великого значення. Зазвичай (за межами коду-гольфу), який ви використовувалиcdqidivxor edx,edxdiv
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 0x8032-бітний код або syscall64-бітний код 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 байти для ініціалізації.