mov
-immediate jest drogi dla stałych
To może być oczywiste, ale wciąż będę to tutaj umieszczał. Zasadniczo opłaca się myśleć o reprezentacji liczby na poziomie bitowym, gdy trzeba zainicjować wartość.
Inicjalizacja za eax
pomocą 0
:
b8 00 00 00 00 mov $0x0,%eax
należy skrócić (w celu zwiększenia wydajności i rozmiaru kodu ) do
31 c0 xor %eax,%eax
Inicjalizacja za eax
pomocą -1
:
b8 ff ff ff ff mov $-1,%eax
można skrócić do
31 c0 xor %eax,%eax
48 dec %eax
lub
83 c8 ff or $-1,%eax
Lub bardziej ogólnie, dowolna 8-bitowa wartość z rozszerzonym znakiem może być utworzona w 3 bajtach z push -12
(2 bajty) / pop %eax
(1 bajt). Działa to nawet w przypadku rejestrów 64-bitowych bez dodatkowego prefiksu REX; push
/ pop
default operand-size = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Lub biorąc pod uwagę znaną stałą w rejestrze, możesz utworzyć inną pobliską stałą za pomocą lea 123(%eax), %ecx
(3 bajtów). Jest to przydatne, jeśli potrzebujesz wyzerowanego rejestru i stałej; xor-zero (2 bajty) + lea-disp8
(3 bajty).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Zobacz także Ustaw efektywnie wszystkie bity w rejestrze procesora na 1
push 200; pop edx
- 3 bajtów do inicjalizacji.dec
np.xor eax, eax; dec eax
push imm8
/pop reg
ma 3 bajty i jest fantastyczny dla stałych 64-bitowych na x86-64, gdziedec
/inc
to 2 bajty. Ipush r64
/pop 64
(2 bajty) może nawet zastąpić 3 bajtymov r64, r64
(3 bajty REX). Zobacz także Ustaw efektywnie wszystkie bity w rejestrze procesora na 1 dla takich rzeczy, jaklea eax, [rcx-1]
dana znana wartość weax
(np. Jeśli potrzebujesz wyzerowanego rejestru i innej stałej, po prostu użyj LEA zamiast push / popW wielu przypadkach instrukcje oparte na akumulatorze (tj. Te, które przyjmują
(R|E)AX
za argument docelowy) są o 1 bajt krótsze niż instrukcje w ogólnym przypadku; zobacz to pytanie na StackOverflow.źródło
al, imm8
przypadki specjalne, takie jakor al, 0x20
/sub al, 'a'
/cmp al, 'z'-'a'
/ja .non_alphabetic
po 2 bajty, zamiast 3. Użycieal
danych znakowych również pozwalalodsb
i / lubstosb
. Lub użyjal
do przetestowania czegoś o niskim bajcie EAX, na przykładlodsd
/test al, 1
/setnz cl
sprawia, że cl = 1 lub 0 dla parzystych / nieparzystych. Ale w rzadkim przypadku, gdy potrzebujesz natychmiastowej wersji 32-bitowej, to na pewnoop eax, imm32
, tak jak w mojej odpowiedzi kluczowej barwyWybierz konwencję połączeń, aby wstawić argumenty tam, gdzie chcesz.
Językiem twojej odpowiedzi jest asm (właściwie kod maszynowy), więc traktuj to jako część programu napisanego w asm, a nie w C-kompilowanej dla x86. Twoja funkcja nie musi być łatwo wywoływana z C przy użyciu dowolnej standardowej konwencji wywoływania. To niezły bonus, jeśli nie kosztuje dodatkowych bajtów.
W czystym programie asm normalne jest, że niektóre funkcje pomocnicze używają konwencji wywoływania, która jest dla nich wygodna i dla ich rozmówcy. Takie funkcje dokumentują swoją konwencję wywoływania (wejścia / wyjścia / clobbers) z komentarzami.
W rzeczywistości nawet programy asm (jak sądzę) zwykle używają spójnych konwencji wywoływania dla większości funkcji (szczególnie w różnych plikach źródłowych), ale każda ważna funkcja może zrobić coś specjalnego. W grze w golfa optymalizujesz bzdury za pomocą jednej funkcji, więc oczywiście jest to ważne / specjalne.
Aby przetestować swoją funkcję z poziomu programu C, możesz napisać opakowanie, które umieszcza argumenty w odpowiednich miejscach, zapisuje / przywraca wszelkie dodatkowe rejestry, które kasujesz, i umieszcza wartość zwracaną,
e/rax
jeśli jeszcze jej nie było.Granice tego, co rozsądne: wszystko, co nie nakłada nieuzasadnionego obciążenia na osobę dzwoniącą:
Wymaganie, aby DF (flaga kierunku łańcucha dla
lods
/stos
/ itd.) Była czysta (w górę) przy wywołaniu / ret jest normalne. Zgoda na niezdefiniowanie podczas połączenia / połączenia byłaby w porządku. Wymaganie, aby zostało wyczyszczone lub ustawione przy wejściu, ale pozostawienie go zmodyfikowanego po powrocie byłoby dziwne.Zwracanie wartości FP w x87
st0
jest rozsądne, ale zwracaniest3
ze śmieciami w innym rejestrze x87 nie jest. Dzwoniący musiałby wyczyścić stos x87. Nawet zwracanie sięst0
z niepustymi rejestrami wyższych stosów również byłoby wątpliwe (chyba że zwracasz wiele wartości).call
, podobnie jak[rsp]
twój adres zwrotny. Państwo może uniknąćcall
/ret
na x86 przy użyciu łącza rejestr jaklea rbx, [ret_addr]
/jmp function
i zwrot zjmp rbx
, ale to nie jest „rozsądne”. To nie jest tak wydajne jak call / ret, więc nie jest to coś, co można znaleźć w prawdziwym kodzie.Przypadki graniczne: napisz funkcję, która tworzy sekwencję w tablicy, biorąc pod uwagę pierwsze 2 elementy jako argumenty funkcji . Zdecydowałem , że osoba wywołująca zapisze początek sekwencji w tablicy i po prostu przekaże wskaźnik do tablicy. To zdecydowanie nagina wymagania pytania. Uważałem biorąc args pakowane w
xmm0
zamovlps [rdi], xmm0
, co byłoby również dziwne konwencja powołanie.Zwraca wartość logiczną w FLAGACH (kody warunków)
Wykonują to wywołania systemowe OS X (
CF=0
oznacza brak błędu): czy używanie rejestru flag jako logicznej wartości zwracanej jest uważane za złą praktykę? .Każdy warunek, który można sprawdzić za pomocą jednego JCC, jest całkowicie uzasadniony, szczególnie jeśli można wybrać taki, który ma semantyczne znaczenie dla problemu. (np. funkcja porównania może ustawić flagi, więc
jne
zostaną one wzięte, jeśli nie będą równe).Wymagaj, aby wąskie argumenty (jak a
char
) były znakami, lub zero rozszerzane do 32 lub 64 bitów.Nie jest to nierozsądne; Użycie
movzx
lubmovsx
uniknięcie częściowego spowolnienia rejestru jest normalne w nowoczesnej wersji x86 asm. W rzeczywistości clang / LLVM już tworzy kod, który zależy od nieudokumentowanego rozszerzenia konwencji wywoływania Systemu x86-64 System V: argumenty węższe niż 32 bity są znakami lub zero rozszerzone do 32 bitów przez osobę dzwoniącą .Możesz udokumentować / opisać rozszerzenie do 64 bitów, pisząc
uint64_t
lubint64_t
w swoim prototypie, jeśli chcesz. np. możesz użyćloop
instrukcji, która wykorzystuje całe 64 bity RCX, chyba że użyjesz prefiksu rozmiaru adresu, aby zastąpić rozmiar do 32-bitowego ECX (tak naprawdę, rozmiar adresu nie rozmiar operandu).Zauważ, że
long
jest to tylko 32-bitowy typ w 64-bitowym ABI dla Windows i ABI dla Linux x32 ;uint64_t
jest jednoznaczny i krótszy niż typunsigned long long
.Istniejące konwencje połączeń:
Windows 32-bit
__fastcall
, już zasugerowany przez inną odpowiedź : liczba całkowita argumentuje wecx
iedx
.x86-64 System V : przekazuje wiele argumentów do rejestrów i ma wiele rejestrów z zaplombowanymi wywołaniami, których można używać bez prefiksów REX. Co ważniejsze, faktycznie wybrano, aby umożliwić kompilatorom wstawianie
memcpy
lub zapisywanie takrep movsb
łatwo: pierwsze 6 argumentów liczb całkowitych / wskaźników jest przekazywanych w RDI, RSI, RDX, RCX, R8, R9.Jeśli twoja funkcja używa
lodsd
/stosd
wewnątrz pętli, która działarcx
razy (zloop
instrukcją), możesz powiedzieć „wywoływalne z C jakint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
w konwencji wywoływania Systemu x86-64”. przykład: chromakey .32-bitowy GCC
regparm
: Argumenty liczb całkowitych w EAX , ECX, EDX, return w EAX (lub EDX: EAX). Posiadanie pierwszego argumentu w tym samym rejestrze co wartość zwracana pozwala na pewne optymalizacje, takie jak ten przypadek z przykładowym wywoływaczem i prototypem z atrybutem funkcji . I oczywiście AL / EAX jest specjalny dla niektórych instrukcji.Linux x32 ABI używa 32-bitowych wskaźników w trybie długim, dzięki czemu można zapisać prefiks REX podczas modyfikowania wskaźnika ( przykładowy przypadek użycia ). Nadal możesz używać 64-bitowego rozmiaru adresu, chyba że masz w rejestrze 32-bitową ujemną liczbę całkowitą z rozszerzeniem zera (tak więc byłaby to duża wartość bez znaku
[rdi + rdx]
).Zauważ, że
push rsp
/pop rax
ma 2 bajty i jest ekwiwalentemmov rax,rsp
, więc nadal możesz kopiować pełne rejestry 64-bitowe w 2 bajtach.źródło
ret 16
; nie podają adresu zwrotnego, nie wypychają tablicy, a następniepush rcx
/ret
. Dzwoniący musiałby znać rozmiar tablicy lub zapisać RSP gdzieś poza stosem, aby się znaleźć.W przypadku AL / AX / EAX należy używać kodowania skróconego specjalnego przypadku oraz innych krótkich formularzy i instrukcji jednobajtowych
Przykłady zakładają tryb 32/64-bitowy, w którym domyślny rozmiar operandu to 32 bity. Prefiks wielkości argumentu zmienia instrukcję na AX zamiast EAX (lub odwrotnie w trybie 16-bitowym).
inc/dec
rejestr (inny niż 8-bitowy):inc eax
/dec ebp
. (Nie x86-64:0x4x
bajty opcode zostały zmienione na prefiksy REX, więcinc r/m32
jest to jedyne kodowanie).8-bitowy
inc bl
jest 2 bajty, z użycieminc r/m8
kodu operacji / M + Modr argumentu operacji kodowania . Więc używaćinc ebx
do przyrostubl
, czy jest to bezpieczne. (np. jeśli nie potrzebujesz wyniku ZF w przypadkach, gdy górne bajty mogą być niezerowe).scasd
:e/rdi+=4
, wymaga, aby rejestr wskazywał na czytelną pamięć. Czasami przydatne, nawet jeśli nie obchodzi cię wynik FLAGI (jakcmp eax,[rdi]
/rdi+=4
). W trybie 64-bitowymscasb
może działać jako 1-bajtowyinc rdi
, jeśli lodsb lub stosb nie są przydatne.xchg eax, r32
: To gdzie 0x90 NOP pochodzi z:xchg eax,eax
. Przykład: ponownie ułóż 3 rejestry z dwiemaxchg
instrukcjami w pętlicdq
/ dla GCD w 8 bajtach, gdzie większość instrukcji jest jednobajtowa, w tym nadużycie / zamiast /idiv
inc ecx
loop
test ecx,ecx
jnz
cdq
: znak rozszerza EAX do EDX: EAX, tzn. kopiuje wysoki bit EAX do wszystkich bitów EDX. Aby utworzyć zero ze znanymi nieujemnymi lub uzyskać 0 / -1, aby dodać / sub lub maskować. Lekcja historii x86:cltq
vs.movslq
oraz mnemoniki AT&T vs. Intel dla tego i pokrewnychcdqe
.lodsb / d : like
mov eax, [rsi]
/rsi += 4
without clobbering flags. (Zakładając, że DF jest jasne, jakie standardowe konwencje wywoływania wymagają przy wprowadzaniu funkcji.) Również stosb / d, czasami scas, a rzadziej movs / cmps.push
/pop reg
. np. w trybie 64-bitowympush rsp
/pop rdi
ma 2 bajty, alemov rdi, rsp
potrzebuje prefiksu REX i ma 3 bajty.xlatb
istnieje, ale rzadko jest użyteczny. Dużej tabeli odnośników należy unikać. Nigdy też nie znalazłem zastosowania dla instrukcji AAA / DAA lub innych instrukcji BCD lub 2-ASCII.1 bajt
lahf
/sahf
rzadko są przydatne. Ty mógłlahf
/and ah, 1
jako alternatywasetc ah
, ale nie jest to zwykle użyteczne.A konkretnie w przypadku CF
sbb eax,eax
jest 0 / -1, a nawet nieudokumentowany, ale powszechnie obsługiwany 1-bajtowysalc
(zestaw AL z Carry), który skutecznie działasbb al,al
bez wpływu na flagi. (Usunięte w x86-64). Użyłem SALC w Wyzwaniu uznania użytkownika nr 1: Dennis ♦ .1-bajtowy
cmc
/clc
/stc
(odwrócenie („uzupełnienie”), wyczyszczenie lub zestaw CF) są rzadko przydatne, chociaż znalazłem zastosowaniecmc
w dodawaniu o rozszerzonej precyzji z podstawowymi fragmentami 10 ^ 9. Aby bezwarunkowo ustawić / wyczyścić CF, zwykle należy to zrobić jako część innej instrukcji, np.xor eax,eax
Czyści CF, a także EAX. Nie ma równoważnych instrukcji dla innych flag stanu, tylko DF (kierunek ciągu) i IF (przerwania). Flaga przenoszenia jest specjalna dla wielu instrukcji; shift ustawia to,adc al, 0
może dodać go do AL w 2 bajtach, a wspomniałem wcześniej o nieudokumentowanej SALC.std
/cld
rzadko wydaje się tego warte . Zwłaszcza w kodzie 32-bitowym lepiej jest po prostu użyćdec
wskaźnika imov
operandu źródła pamięci w instrukcji ALU zamiast ustawiać DF solodsb
/stosb
go w dół zamiast w górę. Zazwyczaj jeśli trzeba w dół w ogóle, trzeba jeszcze inny wskaźnik idzie w górę, tak że trzeba więcej niż jedenstd
, acld
w całej funkcji do wykorzystanialods
/stos
dla obu stron. Zamiast tego po prostu użyj instrukcji strunowych dla kierunku w górę. (Standardowe konwencje wywoływania gwarantują DF = 0 przy wprowadzaniu funkcji, więc można założyć, że za darmo bez użyciacld
.)Historia 8086: dlaczego te kodowania istnieją
W oryginalnym 8086, AX był wyjątkowy: instrukcje jak
lodsb
/stosb
,cbw
,mul
/div
i inni używają go w sposób dorozumiany. Oczywiście nadal tak jest; obecny x86 nie upuścił żadnego z kodów 8086 (przynajmniej żadnego z oficjalnie udokumentowanych). Ale później procesory dodały nowe instrukcje, które dały lepsze / bardziej wydajne sposoby robienia rzeczy bez uprzedniego kopiowania lub zamiany ich na AX. (Lub do EAX w trybie 32-bitowym.)np. 8086 brakowało później dodatków takich jak
movsx
/movzx
aby załadować lub przenieść + przedłużyć znak lub 2 i 3 operandimul cx, bx, 1234
, które nie dają wyniku w połowie i nie mają żadnych ukrytych argumentów.Ponadto głównym wąskim gardłem 8086 było pobieranie instrukcji, więc optymalizacja pod kątem rozmiaru kodu była wtedy ważna dla wydajności . Projektant ISA z 8086 (Stephen Morse) poświęcił dużo miejsca na kodowanie opcodu na specjalne przypadki dla AX / AL, w tym specjalne (E) AX / AL-docelowe kody dla wszystkich podstawowych instrukcji ALU natychmiast-src , po prostu opcode + natychmiast bez bajtu ModR / M. 2 bajty
add/sub/and/or/xor/cmp/test/... AL,imm8
lubAX,imm16
lub (w trybie 32-bitowym)EAX,imm32
.Ale nie ma specjalnego przypadku
EAX,imm8
, więc zwykłe kodowanie ModR / Madd eax,4
jest krótsze.Zakładamy, że jeśli będziesz pracować nad niektórymi danymi, będziesz chciał mieć je w AX / AL, więc zamiana rejestru na AX była czymś, co możesz chcieć zrobić, może nawet częściej niż kopiowanie rejestru do AX za pomocą
mov
.Wszystko w kodowaniu instrukcji 8086 obsługuje ten paradygmat, od instrukcji takich jak
lodsb/w
do wszystkich kodowań specjalnych przypadków dla bezpośrednich znaków w EAX po ich niejawne użycie nawet do mnożenia / dzielenia.Nie daj się ponieść emocjom; nie jest automatycznie wygraną zamiana wszystkiego na EAX, szczególnie jeśli potrzebujesz natychmiastowego dostępu do rejestrów 32-bitowych zamiast 8-bitowych. Lub jeśli potrzebujesz przeplatać operacje na wielu zmiennych w rejestrach jednocześnie. Lub jeśli korzystasz z instrukcji z 2 rejestrami, w ogóle nie następuje to natychmiast.
Ale zawsze należy pamiętać: czy robię coś, co byłoby krótsze w EAX / AL? Czy mogę zmienić układ, aby mieć to w AL, lub czy obecnie lepiej wykorzystuję AL z tym, do czego już go używam.
Swobodnie miksuj operacje 8-bitowe i 32-bitowe, aby czerpać korzyści, gdy tylko jest to bezpieczne (nie musisz przeprowadzać operacji w pełnym rejestrze ani nic takiego).
źródło
cdq
jest użyteczny, dladiv
któregoedx
w wielu przypadkach wymaga wyzerowania .cdq
przed niepodpisaniem,div
jeśli wiesz, że twoja dywidenda jest niższa niż 2 ^ 31 (tj. nie jest ujemna, gdy traktowana jest jak podpisana), lub jeśli użyjesz jej przed ustawieniemeax
potencjalnie dużej wartości. Normalnie (poza code-golf) chcesz użyćcdq
jako konfiguracja doidiv
ixor edx,edx
przeddiv
Użyj
fastcall
konwencjiPlatforma x86 ma wiele konwencji wywoływania . Powinieneś użyć tych, które przekazują parametry w rejestrach. W X86_64 kilka pierwszych parametrów jest przekazywanych do rejestrów, więc nie ma problemu. Na platformach 32-bitowych domyślna konwencja wywoływania (
cdecl
) przekazuje parametry na stosie, co nie jest dobre dla gry w golfa - dostęp do parametrów na stosie wymaga długich instrukcji.Podczas korzystania
fastcall
z platform 32-bitowych zwykle przekazywane są 2 pierwsze parametryecx
iedx
. Jeśli twoja funkcja ma 3 parametry, możesz rozważyć wdrożenie jej na platformie 64-bitowej.Prototypy funkcji C dla
fastcall
konwencji (wzięte z tej przykładowej odpowiedzi ):źródło
Odejmij -128 zamiast dodać 128
Sam dodaj -128 zamiast odjąć 128
źródło
< 128
w,<= 127
aby zmniejszyć wielkość natychmiastowego argumentucmp
lub gcc zawsze woli przestawiać porównuje, aby zmniejszyć jasność, nawet jeśli nie jest to -129 vs. -128.Utwórz 3 zera za pomocą
mul
(następnieinc
/,dec
aby uzyskać +1 / -1 oraz zero)Możesz wyzerować eax i edx, mnożąc przez zero w trzecim rejestrze.
spowoduje, że EAX, EDX i EBX będą miały zero w zaledwie czterech bajtach. Możesz wyzerować EAX i EDX w trzech bajtach:
Ale od tego punktu początkowego nie można uzyskać rejestru o trzeciej wartości zerowej w jeszcze jednym bajcie lub rejestru +1 lub -1 w kolejnych 2 bajtach. Zamiast tego użyj techniki Mul.
Przykładowy przypadek użycia: konkatenacja liczb Fibonacciego w systemie binarnym .
Zauważ, że po
LOOP
zakończeniu pętli ECX będzie wynosić zero i może być użyty do zerowania EDX i EAX; nie zawsze musisz stworzyć pierwsze zeroxor
.źródło
Rejestry i flagi procesora znajdują się w znanych stanach uruchamiania
Możemy założyć, że procesor jest w znanym i udokumentowanym stanie domyślnym w oparciu o platformę i system operacyjny.
Na przykład:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
źródło
_start
. Więc tak, uczciwą grą jest skorzystanie z tego, jeśli piszesz program zamiast funkcji. Zrobiłem to w Extreme Fibonacci . (W dynamicznie połączonego pliku wykonywalnego, ld.so przebiegów przed skokiem do swoich_start
, a nie śmieci pozostawić w rejestrach, ale to tylko statyczny kod.)Aby dodać lub odjąć 1, użyj jednego bajtu
inc
lubdec
instrukcji, które są mniejsze niż wielobajtowe instrukcje dodawania i odejmowania .źródło
inc/dec r32
z numerem rejestru zakodowanym w kodzie operacji. Czyliinc ebx
1 bajt, aleinc bl
2. Wciąż mniejszy niżadd bl, 1
oczywiście dla rejestrów innych niżal
. Zauważ też, żeinc
/dec
pozostaw CF niezmodyfikowany, ale zaktualizuj pozostałe flagi.lea
do matematykiJest to prawdopodobnie jedna z pierwszych rzeczy, których uczy się o x86, ale zostawiam to tutaj jako przypomnienie.
lea
może służyć do mnożenia przez 2, 3, 4, 5, 8 lub 9 i dodawania przesunięcia.Na przykład, aby obliczyć
ebx = 9*eax + 3
w jednej instrukcji (w trybie 32-bitowym):Tutaj jest bez przesunięcia:
Łał! Oczywiście
lea
można go również wykorzystać do obliczeń matematycznych, takich jakebx = edx + 8*eax + 3
obliczanie indeksowania tablic.źródło
lea eax, [rcx + 13]
jest to wersja bez dodatkowych prefiksów dla trybu 64-bitowego. 32-bitowy rozmiar argumentu (dla wyniku) i 64-bitowy rozmiar adresu (dla wejść).Instrukcje pętli i łańcuchów są mniejsze niż alternatywne sekwencje instrukcji. Najbardziej użyteczna jest ta,
loop <label>
która jest mniejsza niż dwie sekwencje instrukcjidec ECX
ijnz <label>
, ilodsb
jest mniejsza niżmov al,[esi]
iinc si
.źródło
mov
małe natychmiast przechodzi do niższych rejestrów, jeśli dotyczyJeśli już wiesz, że górnymi bitami rejestru są 0, możesz użyć krótszej instrukcji, aby przenieść natychmiast do niższych rejestrów.
przeciw
Użyj
push
/pop
dla imm8 do zera górnych bitówPodziękowania dla Petera Cordesa.
xor
/mov
ma 4 bajty, alepush
/pop
ma tylko 3!źródło
mov al, 0xa
jest dobry, jeśli nie potrzebujesz go z zerowym rozszerzeniem do pełnego rejestru. Ale jeśli to zrobisz, xor / mov ma 4 bajty vs. 3 dla push imm8 / pop lublea
z innej znanej stałej. Może to być przydatne w połączeniu zmul
zerowaniem 3 rejestrów w 4 bajtach lubcdq
, jeśli potrzebujesz wielu stałych.[0x80..0xFF]
których nie można przedstawić jako imm8 z rozszerzonym znakiem. Lub jeśli znasz już górne bajty, np.mov cl, 0x10
Poloop
instrukcji, ponieważ jedynym sposobem,loop
aby nie skakać, jest jej wykonaniercx=0
. (Myślę, że to powiedziałeś , ale twój przykład używaxor
). Możesz nawet użyć niskiego bajtu rejestru dla czegoś innego, o ile coś innego ustawia go ponownie na zero (lub cokolwiek innego), kiedy skończysz. np. mój program Fibonacciego trzyma-1024
w ebx i używa bl.xchg eax, r32
) np.mov bl, 10
/dec bl
/jnz
Więc twój kod nie dba o wysokie bajty RBX.W FLAGI są ustawione po wielu instrukcjach
Po wielu instrukcjach arytmetycznych flagi przenoszenia (niepodpisane) i flagi przepełnienia (podpisane) są ustawiane automatycznie ( więcej informacji ). Flaga Znaku i Flaga Zera są ustawiane po wielu operacjach arytmetycznych i logicznych. Można tego użyć do rozgałęzienia warunkowego.
Przykład:
ZF jest ustawiony przez tę instrukcję, więc możemy go użyć do warunkowego rozgałęzienia.
źródło
test al,1
; zwykle nie dostajesz tego za darmo. (Luband al,1
utworzyć liczbę całkowitą 0/1 w zależności od nieparzystej / parzystej.)test
/cmp
”, to byłby to dość prosty początkujący x86, ale nadal warty upvote.Używaj pętli do-while zamiast pętli while
Nie jest to specyficzne dla x86, ale jest powszechnie stosowaną wskazówką dla początkujących. Jeśli wiesz, że pętla while uruchomi się co najmniej raz, przepisanie pętli jako pętli do-while, ze sprawdzaniem stanu pętli na końcu, często zapisuje 2-bajtową instrukcję skoku. W szczególnym przypadku możesz nawet użyć
loop
.źródło
do{}while()
występuje naturalny idiom zapętlania w montażu (szczególnie pod względem wydajności). Zauważ też, że 2-bajtowajecxz
/jrcxz
zanim pętla działa bardzo dobrze,loop
aby poradzić sobie ze sprawą „musi działać„ zero razy ”„ wydajnie ”(na rzadkich procesorach, gdzieloop
nie jest wolna).jecxz
jest również użyteczny w pętli, aby zaimplementowaćwhile(ecx){}
, zjmp
na dole.Używaj dowolnych dogodnych konwencji połączeń
System V x86 używa stosu i System V x86-64 zastosowania
rdi
,rsi
,rdx
,rcx
, itd. Dla parametrów wejściowych, arax
jako wartość zwracana, ale jest to całkowicie uzasadnione, aby użyć własnego konwencja wywołania. __fastcall używaecx
iedx
jako parametry wejściowe, a inne kompilatory / systemy operacyjne stosują własne konwencje . Użyj stosu i innych rejestrów jako wejścia / wyjścia, gdy jest to wygodne.Przykład: Powtarzalny licznik bajtów , przy użyciu sprytnej konwencji wywoływania dla rozwiązania 1-bajtowego.
Meta: Zapisywanie danych wejściowych do rejestrów , Zapisywanie danych wyjściowych do rejestrów
Inne zasoby: uwagi Agner Fog na temat zwoływania konwencji
źródło
int 0x80
konfiguracja.int 0x80
w 32-bitowym kodzie lubsyscall
w 64-bitowym kodzie, aby wywołaćsys_write
, jest jedynym dobrym sposobem. Tego właśnie użyłem do Extreme Fibonacciego . W 64-bitowym kodzie__NR_write = 1 = STDOUT_FILENO
, więc możeszmov eax, edi
. Lub jeśli górne bajty EAX są równe zero,mov al, 4
w kodzie 32-bitowym. Można teżcall printf
czyputs
, jak sądzę, i napisać „asm x86 dla Linux + glibc” odpowiedź. Myślę, że rozsądne jest nie liczenie przestrzeni wejściowej PLT, GOT ani samego kodu biblioteki.char*buf
i wygenerował w nim ciąg znaków z ręcznym formatowaniem. np. jak ten (niezręcznie zoptymalizowany pod kątem prędkości) asm FizzBuzz , w którym zapisałem dane ciągów do rejestru, a następnie je zapisałemmov
, ponieważ ciągi były krótkie i stałej długości.Używaj ruchów
CMOVcc
i zestawów warunkowychSETcc
Jest to bardziej przypomnienie dla mnie, ale istnieją instrukcje zestawu warunkowego i instrukcje przenoszenia warunkowego na procesorach P6 (Pentium Pro) lub nowszych. Istnieje wiele instrukcji opartych na jednej lub więcej flag ustawionych w EFLAGS.
źródło
cmov
ma 2-bajtowy kod operacji (0F 4x +ModR/M
), więc minimum 3 bajty. Ale źródłem jest r / m32, więc możesz warunkowo załadować 3 bajty. Inne niż rozgałęzianie,setcc
jest przydatne w większej liczbie przypadków niżcmovcc
. Mimo to rozważ cały zestaw instrukcji, a nie tylko podstawowe instrukcje 386. (Chociaż instrukcje SSE2 i BMI / BMI2 są tak duże, że rzadko są użyteczne.rorx eax, ecx, 32
To 6 bajtów, więcej niż mov + ror. Niezła wydajność, nie golf, chyba że POPCNT lub PDEP uratuje wiele isns)setcc
.Zaoszczędź na
jmp
bajtach, ustawiając w if / then zamiast if / then / elseJest to z pewnością bardzo podstawowe, pomyślałem, że opublikuję to jako coś do przemyślenia podczas gry w golfa. Jako przykład rozważ następujący prosty kod do zdekodowania znaku szesnastkowego:
Można to skrócić o dwa bajty, pozwalając, aby przypadek „wtedy” zamienił się w przypadek „inny”:
źródło
sub
opóźnienie na ścieżce krytycznej dla jednego przypadku nie jest częścią łańcucha zależności przenoszonego przez pętlę (jak tutaj, gdzie każda cyfra wejściowa jest niezależna aż do scalenia 4-bitowych fragmentów ). Ale tak czy inaczej +1. BTW, twój przykład ma oddzielną pominiętą optymalizację: jeśli i tak będziesz potrzebowaćmovzx
na końcu,sub $imm, %al
nie używaj EAX, aby skorzystać z 2-bajtowego kodowania bez modrmop $imm, %al
.cmp
, wykonującsub $'A'-10, %al
;jae .was_alpha
;add $('A'-10)-'0'
. (Myślę, że mam właściwą logikę). Pamiętaj, że'A'-10 > '9'
nie ma dwuznaczności. Odejmowanie poprawki dla litery spowoduje zawinięcie cyfry dziesiętnej. Jest to bezpieczne, jeśli zakładamy, że nasze dane wejściowe są poprawne hex, tak jak twoje.Możesz pobrać kolejne obiekty ze stosu, ustawiając esi na esp i wykonując sekwencję regów lodsd / xchg, eax.
źródło
pop eax
/pop edx
/ ...? Jeśli chcesz zostawić je na stosie, możeszpush
je wszystkie później przywrócić ESP, nadal 2 bajty na obiekt bez potrzebymov esi,esp
. A może chodziło Ci o 4-bajtowe obiekty w 64-bitowym kodzie, gdziepop
otrzymalibyśmy 8 bajtów? BTW, możesz nawet użyćpop
do przełączania bufora z lepszą wydajnością niżlodsd
np. W celu dodania rozszerzonej precyzji w Extreme FibonacciegoDla codegolf i ASM: Użyj instrukcji, używaj tylko rejestrów, push pop, minimalizuj pamięć rejestrów lub pamięć natychmiastową
źródło
Aby skopiować rejestr 64-bitowy, użyj
push rcx
;pop rdx
zamiast 3 bajtówmov
.Domyślny rozmiar argumentu push / pop to 64-bit bez potrzeby używania prefiksu REX.
(Prefiks wielkości operandu może zastąpić rozmiar push / pop do 16-bitowego, ale 32-bitowego rozmiaru operandu push / pop nie można kodować w trybie 64-bitowym nawet przy REX.W = 0).
Jeśli jeden lub oba rejestry są
r8
…r15
, użyj,mov
ponieważ push i / lub pop będą wymagały prefiksu REX. W najgorszym przypadku to faktycznie traci, jeśli oba potrzebują prefiksów REX. Oczywiście w kodzie golfowym zwykle powinieneś unikać r8..r15.Możesz zachować źródło bardziej czytelne podczas programowania dzięki temu makro NASM . Pamiętaj tylko, że działa na 8 bajtach poniżej RSP. (W czerwonej strefie w systemie x86-64 System V). Ale w normalnych warunkach jest to zastępczy zamiennik dla wersji 64-bitowej
mov r64,r64
lubmov r64, -128..127
Przykłady:
xchg
Częścią przykład dlatego, że czasami trzeba uzyskać wartość w EAX lub RAX i nie dbają o zachowanie starej kopii. Push / pop nie pomaga jednak w wymianie.źródło