Yanıtlar:
mov
-sıkı sabitler için pahalıdırBu açık olabilir, ama yine de buraya koyacağım. Genel olarak, bir değeri başlatmanız gerektiğinde bir sayının bit düzeyindeki temsilini düşünmek için fayda sağlar.
eax
ile 0
:b8 00 00 00 00 mov $0x0,%eax
kısaltılmalıdır ( performans ve kod boyutu için )
31 c0 xor %eax,%eax
eax
ile -1
:b8 ff ff ff ff mov $-1,%eax
kısaltılabilir
31 c0 xor %eax,%eax
48 dec %eax
veya
83 c8 ff or $-1,%eax
Veya daha genel olarak, herhangi bir 8 bitlik işaret genişletilmiş değer push -12
(2 bayt) / pop %eax
(1 bayt) ile 3 baytta oluşturulabilir. Bu, ilave REX ön eki olmayan 64 bitlik kayıtlar için bile çalışır; push
/ pop
default işlem büyüklüğü = 64.
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
Veya bir sicile bilinen bir sabit verildiğinde, lea 123(%eax), %ecx
(3 byte) kullanarak yakındaki başka bir sabit oluşturabilirsiniz . Sıfırlanmış bir sicile ve bir sabite ihtiyacınız varsa bu kullanışlıdır ; xor-zero (2 bayt) + lea-disp8
(3 bayt).
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
Ayrıca bkz . CPU kayıt defterindeki tüm bitleri verimli bir şekilde 1'e ayarlama
dec
, örneğinxor eax, eax; dec eax
push imm8
/ pop reg
3 bayt ve x86-64, 64 bit sabitleri için harika dec
/ inc
2 bayt. Ve push r64
/ pop 64
(2 bayt) 3 bayt mov r64, r64
(REX ile 3 bayt) bile değiştirebilirsiniz . Ayrıca bkz . CPU kaydındaki tüm bitlerilea eax, [rcx-1]
bilinen bir değer verilenler için verimli bir şekilde 1'e ayarlayıneax
(örn. Sıfırlanmış bir kayıt ve başka bir sabit gerekiyorsa, sadece
Birçok durumda, akümülatör tabanlı talimatlar (yani (R|E)AX
hedef operand olarak geçenler ) genel durum talimatlarından 1 bayt daha kısadır; StackOverflow bu soruya bakın .
al, imm8
gibi her biri 2 bayt olmak üzere 3'teki özel durumlardır . Karakter verilerini kullanmak ayrıca ve / veya sağlar . Ya da EAX'in düşük byte'ı hakkında bir şeyler test etmek için kullanın , / / yapar cl = 1 veya 0 tek / çift. Ancak hemen 32-bit acil ihtiyaç duyduğunuz nadir durumda, elbette , chroma-key cevabımdaki gibior al, 0x20
sub al, 'a'
cmp al, 'z'-'a'
ja .non_alphabetic
al
lodsb
stosb
al
lodsd
test al, 1
setnz cl
op eax, imm32
Cevabınızın dili asm'dir (aslında makine kodu), bu yüzden x-c86 için C derlenmiş olarak değil, asm ile yazılmış bir programın parçası olarak kabul edin. İşleviniz, herhangi bir standart çağrı kuralıyla C'den kolayca aranabilir olmak zorunda değildir. Yine de herhangi bir ekstra bayta mal olmazsa bu güzel bir bonus.
Saf bir asm programında, bazı yardımcı işlevlerin kendileri ve arayanlar için uygun bir çağrı kuralı kullanması normaldir. Bu tür fonksiyonlar çağrı kurallarını (girişler / çıkışlar / clobbers) yorumlarla belgeler.
Gerçek hayatta, programlar bile olsa (sanırım) çoğu işlev için (özellikle farklı kaynak dosyalarda) tutarlı çağrı kuralları kullanma eğilimindedir, ancak verilen herhangi bir önemli işlev özel bir şey yapabilir. Code-golf'da, tek bir fonksiyonun saçmalıklarını optimize ediyorsun, bu yüzden açıkça önemli / özel.
İşlevinizi bir C programından sınamak için , hataları doğru yerlere koyan, clobber yaptığınız fazladan kayıtları kaydeden / geri yükleyen ve e/rax
zaten mevcut değilse geri dönüş değerini koyan bir sarmalayıcı yazabilirsiniz .
Çağrı / ret sırasında DF'nin ( lods
/ stos
/ vb. İçin dize yönü bayrağı ) açık (yukarı) işaretinin normal olması gerekir. Çağrı / tanımlamanın tanımsız olmasına izin vermek tamam olacaktır. Temizlenmesi veya girişe ayarlanması ancak daha sonra geri döndüğünüzde değiştirilmesinin yapılması garip olacaktır.
FP değerlerinin x87'ye döndürülmesi st0
makul, ancak st3
diğer x87 registerlarındaki çöplerle geri dönmek de mümkün değil. Arayan x87 yığınını temizlemek zorunda kalacaktı. st0
Boş olmayan daha yüksek yığın kayıtlarına geri dönmek bile sorgulanabilir (birden fazla değer döndürmediğiniz sürece).
call
, [rsp]
dönüş adresiniz de öyle. Sen edebilirsiniz önlemek call
/ ret
bağlantı kaydını gibi kullanarak x86 üzerinde lea rbx, [ret_addr]
/ jmp function
ile dönüşünü jmp rbx
, ama bu "makul" değil. Bu çağrı / ret kadar verimli değil, bu yüzden gerçek kodla makul bir şekilde bulacağınız bir şey değil.Borderline vakaları: ilk 2 elemanı fonksiyon argümanları olarak verilen bir dizide bir dizi üreten bir fonksiyon yazın . Arayan kişinin dizinin başlangıcını diziye kaydetmesini ve diziye bir gösterici iletmesini seçtim . Bu kesinlikle sorunun gereklerini yerine getiriyor. İçine paketlenmiş args alarak kabul xmm0
için movlps [rdi], xmm0
de garip bir çağrı kuralı olur.
OS X sistem çağrıları bunu yapar ( CF=0
hata olmaz): Bayrak kayıtlarını bir boole dönüş değeri olarak kullanmak kötü bir uygulama olarak mı kabul edilir? .
Bir JCC ile kontrol edilebilecek herhangi bir koşul, özellikle sorunla ilgili herhangi bir anlamsal alakası olan birini seçebiliyorsanız, tamamen makul. (örneğin, bir karşılaştırma işlevi bayrakları ayarlayabilir, bu yüzden jne
eşit olmadıklarında alınacaktır).
char
ya da (a gibi ) işaretlerin ya da sıfırın 32 ya da 64 bite uzatılması gerekir.Bu mantıksız değildir; Modern x86 asm'da kısmi kayıt yavaşlamalarını kullanmak movzx
veya kullanmak normaldir. Aslında, clang / LLVM, zaten x86-64 System V çağrı kuralına belgelenmemiş bir uzantıya bağlı olan bir kod hazırlıyor: 32 bitten daha dar olan işaretler, arayan tarafından 32 bite işaret veya sıfır olarak uzatıldı .movsx
İsterseniz, uzantıyı 64 bit yazıp uint64_t
veya int64_t
prototipinizde belgeleyebilir / tanımlayabilirsiniz . örneğin loop
, boyutu 32 bit ECX'e kadar geçersiz kılmak için bir adres boyutu öneki kullanmıyorsanız, RCX'in tüm 64 bitini kullanan bir komut kullanabilirsiniz.
long
Windows 64-bit ABI ve Linux x32 ABI'de yalnızca 32 bitlik bir tür olduğuna dikkat edin ; uint64_t
belirsiz ve yazmaktan daha kısa unsigned long long
.
Windows 32-bit __fastcall
, zaten başka bir cevap tarafından önerildi : tamsayı args ecx
ve edx
.
x86-64 Sistem V : yazmaçlarda çok fazla hata iletir ve REX önekleri olmadan kullanabileceğiniz çok sayıda çağrı engelli yazmaç vardır. Daha da önemlisi, aslında derleyicilerin satır içi memcpy
ya da memset'e rep movsb
kolay şekilde izin vermesi için seçildi : ilk 6 tamsayı / işaretçi argümanları RDI, RSI, RDX, RCX, R8, R9'da geçirilir.
Eğer fonksiyonunuz, zaman çalıştıran bir döngü içinde lodsd
/ stosd
içinde kullanılıyorsa rcx
( loop
talimat ile), " int foo(int *rdi, const int *rsi, int dummy, uint64_t len)
x86-64 System V arama kuralında olduğu gibi C'den çağrılabilir" diyebilirsiniz . örnek: chromakey .
32-bit GCC regparm
: Tamsayılı EAX , ECX, EDX, EAX (veya EDX: EAX) içinde geri döner. İlk argümanın dönüş değeri ile aynı sicile kaydedilmesi, örnek bir arayanla ve fonksiyon özniteliğine sahip bir prototip gibi , bazı optimizasyonlara izin verir . Ve elbette AL / EAX bazı talimatlar için özeldir.
Linux x32 ABI uzun modda 32 bit işaretçiler kullanır, böylece bir işaretçiyi değiştirirken bir REX öneki kaydedebilirsiniz ( örnek kullanım durumu ). Bir kayıt defterinde sıfır genişletilmiş bir 32 bit negatif tamsayıya sahip değilseniz, bu yüzden 64 bit adres boyutunu kullanabilirsiniz (eğer öyleyse büyük bir işaretsiz değer olacaktır [rdi + rdx]
).
Not o push rsp
/ pop rax
2 byte ve eşdeğerdir mov rax,rsp
yine böylece, kopyalama 2 bayt tam 64 bit kayıtlarını.
ret 16
; dönüş adresini açmazlar, bir diziye basar, sonra push rcx
/ ret
. Arayan kişinin dizi boyutunu bilmesi veya RSP'yi kendisini bulmak için yığının dışında bir yere kaydetmesi gerekirdi.
AL / AX / EAX için özel durum kısa form kodlamalarını ve diğer kısa formları ve tek baytlık talimatları kullanın
Örnekler, varsayılan işlenen boyutunun 32 bit olduğu 32/64-bit modunu varsayar. İşlenen boyutunda bir önek, talimatı EAX yerine AX olarak değiştirir (veya 16 bit modunda tersini).
inc/dec
bir kayıt (8 bit dışında): inc eax
/ dec ebp
. (X86-64 değil: 0x4x
opcode baytları REX önekleri olarak yeniden düzenlenmiştir inc r/m32
, tek kodlamadır.)
8 bit inc bl
, inc r/m8
opcode + ModR / M operand kodlamasını kullanarak 2 bayttır . Yani kullanmak inc ebx
artıma bl
güvenli olduğunu eğer. (örneğin, üst baytların sıfır olmayan durumlarda ZF sonucuna ihtiyacınız yoksa).
scasd
: e/rdi+=4
, yazıcının okunabilir belleği göstermesini gerektirir. Bazen FLAGS sonucunu umursamıyor olsanız bile yararlıdır ( cmp eax,[rdi]
/ gibi rdi+=4
). Ve 64-bit modunda, scasb
bir 1-byte olarak çalışabilirinc rdi
lodsb veya stosb kullanışlı değilse,.
xchg eax, r32
: 0x90 NOP nereden geldiğini şudur: xchg eax,eax
. Örnek: İki 3 kayıtları yeniden düzenlemek xchg
bir yönergelere cdq
/ idiv
döngü 8 byte GCD'nın için bir bağımlılığı dahil talimatları çoğu tek baytlık vardır inc ecx
/ loop
yerine test ecx,ecx
/ 'jnz
cdq
: EAX işaretini EDX: EAX içine genişletir, yani EAX'in yüksek bitini EDX'in tüm bitlerine kopyalar. Negatif olmayan bilinen bir sıfır oluşturmak veya eklemek / sub veya maske ile 0 / -1 almak. x86 tarih dersi: cltq
vs.movslq
ve ayrıca bununla ve bununla ilgili AT&T vs. Intel anımsatıcıları cdqe
.
lodsb / d : Gizemli bayrakların olduğu mov eax, [rsi]
/ rsi += 4
olmadığı gibi . (DF'nin açık olduğunu varsayalım, hangi standart çağrı kurallarının fonksiyon girişi için gerekli olduğunu varsayalım.)
push
/ pop reg
. örneğin 64 bit modunda, push rsp
/ pop rdi
bayt 2'dir, ancak mov rdi, rsp
bir REX ön ekine ve 3 bayttır.
xlatb
var, ancak nadiren kullanışlıdır. Büyük bir arama tablosu kaçınılması gereken bir şeydir. Ayrıca AAA / DAA veya diğer paketlenmiş BCD veya 2 ASCII basamaklı talimatlar için bir kullanım bulamadım.
1 bayt lahf
/ sahf
nadiren kullanışlıdır. Sen could lahf
/ and ah, 1
alternatif olarak setc ah
, ancak genellikle yararlıdır değil.
Ve özellikle CF için, bayrakları etkilemeden etkili sbb eax,eax
bir şekilde yapan 0 / -1, hatta dokümante edilmemiş ancak evrensel olarak desteklenen 1-byte salc
(AL'den Alry)sbb al,al
. (X86-64'te kaldırılmıştır). Kullanıcı Takdir Yarışması # 1: Dennis ♦ 'de SALC kullandım .
1-byte cmc
/ clc
/ stc
(flip ("complement"), clear veya set CF) nadiren kullanışlıdır, ancak 10 ^ 9 boyutunda baz ile genişletilmiş hassasiyete sahip bir kullanım için bir kullanımcmc
buldum . Koşulsuz bir şekilde CF'yi ayarlamak / silmek için, bunun genellikle başka bir talimatın parçası xor eax,eax
olarak olmasını sağlayın , örneğin EAX'in yanı sıra CF'yi de temizler. Diğer durum bayrakları için eşdeğer bir talimat yoktur, sadece DF (string direction) ve IF (interrupts). Taşıma bayrağı birçok talimat için özeldir; vardiya adc al, 0
2 bayt içinde AL ekleyebilirsiniz, ve daha önce belgelenmemiş SALC bahsetti.
std
/ cld
nadiren buna değer görünüyor . Özellikle 32-bit kodda, DF'yi yukarı / aşağı ayarlamak yerine bir ALU komutuna getirmek için sadece dec
bir işaretçi ve a mov
veya bellek kaynağı işlenen üzerinde kullanmak daha iyidir . Genellikle hiç aşağıya ihtiyacınız olursa, yine de yukarı çıkan başka bir göstergeye sahipsiniz, bu yüzden birden fazla ve tüm işlevde / ikisini birden kullanmanız gerekir . Bunun yerine, yalnızca yukarı yön için dize talimatlarını kullanın. (Standart arama kuralları, işlev girişinde DF = 0 değerini garanti eder, bu yüzden kullanmadan ücretsiz olarak kabul edebilirsiniz .)lodsb
stosb
std
cld
lods
stos
cld
Özgün 8086 yılında AX çok özel: talimatlar gibi lodsb
/ stosb
, cbw
, mul
/ div
ve diğerleri örtük kullanabilirsiniz. Bu hala elbette böyle; Mevcut x86, 8086'nın herhangi bir kodunu düşürmedi (en azından resmi olarak belgelenenlerden hiçbiri). Ancak daha sonra CPU'lar, bunları önce AX'e kopyalamak veya değiştirmek zorunda kalmadan işleri yapmak için daha iyi / daha verimli yollar sunan yeni talimatlar ekledi. (Veya 32-bit modunda EAX’e.)
Örneğin, 8086, daha yüksek bir sonuç üretmeyen ve herhangi bir örtülü işlemsel içermeyen, + işareti genişletme veya taşıma movsx
/ movzx
taşıma + işaret genişletme veya 2 ve 3 işlemsel gibi eklemelere imul cx, bx, 1234
sahip değildi.
Ayrıca, 8086'nın ana darboğazı talimat getirme idi, bu nedenle kod boyutu için optimizasyon o zamanki performans için önemliydi . 8086'nın ISA tasarımcısı (Stephen Morse) , AX / AL için özel durumlarda, AQ / AL için özel (E) AX / AL hedef kodları da dahil olmak üzere tüm temel anlık srU ALU talimatları için , sadece opcode + hemen ModR / M bayt yok. 2 bayt add/sub/and/or/xor/cmp/test/... AL,imm8
veya AX,imm16
veya (32 bit modda) EAX,imm32
.
Ancak bunun için özel bir durum yok EAX,imm8
, bu yüzden normal ModR / M kodlaması add eax,4
daha kısa.
Varsayım, eğer bazı veriler üzerinde çalışacaksanız, bunu AX / AL'de isteyeceksinizdir, bu nedenle AX ile bir kaydı değiştirmek, yapmak isteyebileceğiniz bir şeydi, belki de AX'a bir kayıt kopyalamaktan daha sık mov
.
8086 komut kodlamasıyla ilgili her şey, bu paradigmayı, lodsb/w
EAX ile anında gerçekleşen tüm özel durum kodlamaları gibi talimatlardan çoğaltmak / bölmek için bile gizli kullanımına kadar olan talimatlardan destekler .
Uzaklaşmayın; Her şeyi EAX'e takas etmek otomatik olarak bir kazanç değildir, özellikle de 8-bit yerine 32-bit yazmaçlarla hemen kullanmanız gerekiyorsa. Veya birden fazla değişken üzerindeki işlemleri aynı anda kaydettirmeniz gerekiyorsa. Veya 2 yazmaçlı talimatlar kullanıyorsanız, hemen hemen değil.
Ancak daima aklınızda bulundurun: EAX / AL'da daha kısa olacak bir şey yapıyorum mu? Yeniden düzenleyebilir miyim yani AL'da var mı, yoksa şu anda AL'da ne için kullandığımdan daha iyi bir şekilde yararlanıyorum.
Bunu yapmak güvenli olduğunda avantaj sağlamak için 8 bit ve 32 bit işlemlerini serbestçe karıştırın (tam sicile ya da her neyse gerçekleştirmeniz gerekmez).
cdq
birçok durumda div
sıfırlanan ihtiyaçlar için kullanışlıdır edx
.
cdq
önce kötüye div
kullanabilirsiniz eax
. Normalde (dış kod golf dışında) kurulum için ve daha önce kullanacağınızcdq
idiv
xor edx,edx
div
fastcall
Sözleşmeleri kullanx86 platformunun birçok çağrı sözleşmesi vardır . Kayıtlarda parametre iletenleri kullanmalısınız. X86_64'de, ilk birkaç parametre yine de kayıtlara iletilir, bu nedenle sorun yoktur. 32 bit platformlarda, varsayılan çağrı kuralı ( cdecl
) yığın halinde parametreleri iletir; golf oynamak için iyi değildir - yığın üzerinde parametrelere erişmek için uzun talimatlar gerekir.
Kullanırken fastcall
, 32-bit platformlarda, 2 ilk parametreler genellikle geçirilir ecx
ve edx
. Eğer fonksiyonunuz 3 parametre içeriyorsa, bunu 64-bit bir platformda uygulamayı düşünebilirsiniz.
fastcall
Kongre için C işlevi prototipleri ( bu örnek cevaptan alınmıştır ):
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
Samely, 128 çıkarma yerine -128 ekleyin
< 128
içine <= 127
yönelik acil işlenen büyüklüğünü azaltmak için cmp
, ya da gcc hep yeniden düzenleme tercih -129'a karşı -128 olmasa bile büyüklüğü azaltmak için karşılaştırır .
mul
(sonra inc
/ dec
sıfıra ek olarak +1 / -1 almak için /)Üçüncü kayıtta sıfır ile çarparak eax ve edx'i sıfırlayabilirsiniz.
xor ebx, ebx ; 2B ebx = 0
mul ebx ; 2B eax=edx = 0
inc ebx ; 1B ebx=1
EAX, EDX ve EBX’in hepsi yalnızca dört baytta sıfır olacak. EAX ve EDX'i üç baytta sıfırlayabilirsiniz:
xor eax, eax
cdq
Ancak bu başlangıç noktasından, bir daha baytta 3. sıfırlanmış bir kayıt veya bir başka 2 baytta +1 veya -1 kayıt alamazsınız. Bunun yerine, mul tekniğini kullanın.
Örnek kullanım örneği: Fibonacci sayılarını ikili olarak birleştirmek .
Bir LOOP
döngü bittikten sonra ECX'in sıfır olacağını ve EDX ve EAX'i sıfırlamak için kullanılabileceğini unutmayın; her zaman ilk sıfırıyla oluşturmak zorunda değilsin xor
.
İşlemcinin platform ve işletim sistemine bağlı olarak bilinen ve belgelenmiş bir varsayılan durumda olduğunu varsayabiliriz.
Örneğin:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
_start
. Yani evet 's adil oyun o yararlanmak için eğer bunun yerine bir fonksiyonun bir program yazıyoruz. Bunu Extreme Fibonacci'de yaptım . (Dinamik bağlantılı yürütülebilir olarak, ishal ld.so atlama önce adresinden Müşteri _start
ve yapar kayıtlarında izin çöp, ancak statik sadece koddur.)
1 eklemek veya çıkarmak için, bir bayt inc
veya dec
çok baytlı toplama ve alt talimatlardan daha küçük olan talimatları kullanın.
inc/dec r32
op kodda kodlanmış kayıt numarasıyla 1 bayta sahip olduğuna dikkat edin. Öyleyse inc ebx
1 bayt, ancak inc bl
2'dir add bl, 1
. Tabii ki hala küçüktür al
. Ayrıca CF değiştirilmemiş inc
/ dec
bırakın, ancak diğer bayrakları güncelleyin.
lea
matematik içinMuhtemelen bu, x86 hakkında öğrenilen ilk şeylerden biri, ancak burada hatırlatma olarak bırakıyorum. lea
2, 3, 4, 5, 8 veya 9 ile çarpma yapmak ve bir ofset eklemek için kullanılabilir.
Örneğin, ebx = 9*eax + 3
tek komutta hesaplamak için (32 bit modunda):
8d 5c c0 03 lea 0x3(%eax,%eax,8),%ebx
İşte bir mahsup olmadan:
8d 1c c0 lea (%eax,%eax,8),%ebx
Vaov! Tabii ki, dizi indeksleme hesaplamak lea
gibi matematik yapmak için de kullanılabilir ebx = edx + 8*eax + 3
.
lea eax, [rcx + 13]
64-bit modu için hiçbir önek sürümü olduğunu söylemeye değer . 32 bitlik işlenen boyutu (sonuç için) ve 64 bit adres boyutu (girişler için).
Döngü ve dize komutları alternatif komut dizilerinden daha küçüktür. En yararlı olan loop <label>
iki komut dizisi daha küçük olan, dec ECX
ve jnz <label>
, ve lodsb
daha küçük olan mov al,[esi]
ve inc si
.
mov
küçük, uygun olduğunda daha düşük kayıtlara başlarBir kaydın üst bitlerinin zaten 0 olduğunu biliyorsanız, hemen bir kaydı alt kayıtlara taşımak için daha kısa bir komut kullanabilirsiniz.
b8 0a 00 00 00 mov $0xa,%eax
e karşı
b0 0a mov $0xa,%al
push
/ kullanınpop
Peter Cordes'e teşekkür ederim. xor
/ mov
4 bayt, ancak push
/ pop
yalnızca 3!
6a 0a push $0xa
58 pop %eax
mov al, 0xa
Sıfır genişletilmiş tam reg gerekmiyorsa iyidir. Ama yaparsanız, xor / mov, imm8 / pop için veya lea
bilinen başka bir sabitten 4 bayt vs 3'tür . Bu arada yararlı olabilir ile mul
4 bayt 3 sıfır kayıtları veya cdq
sabitlerin çok gerekiyorsa olsa,.
[0x80..0xFF]
, işaret uzatılmış imm8 olarak gösterilemeyen sabitler içindir. Ya da üst baytları zaten biliyorsanız, örneğin mov cl, 0x10
bir loop
talimattan sonra , çünkü zıplamamanın tek yolu, loop
yapılan zamandır rcx=0
. (Sanırım bunu söylediniz , ama örneğinizde bir tane kullanıyor xor
). Kaydın düşük baytını, başka bir şey bittiğinde başka bir şey sıfıra (ya da her neyse) geri koyduğu sürece kullanabilirsiniz. mesela benim Fibonacci programı tutan -1024
ebx'e ve kullanımları bl.
xchg eax, r32
örneğin hariç ) mov bl, 10
/ dec bl
/ jnz
böylece kodunuz RBX'in yüksek baytını önemsemez.
Birçok aritmetik talimattan sonra, Taşıma Bayrağı (imzasız) ve Taşma Bayrağı (imzalı) otomatik olarak ayarlanır ( daha fazla bilgi ). İşaret Bayrağı ve Sıfır Bayrağı birçok aritmetik ve mantıksal işlemden sonra ayarlanır. Bu şartlı dallanma için kullanılabilir.
Örnek:
d1 f8 sar %eax
ZF bu komut ile belirlenir, bu yüzden koşullu dallanma için kullanabiliriz.
test al,1
; genellikle bunu bedavaya alamazsın. (Ya da and al,1
tek / çift oranına bağlı olarak 0/1 tamsayı oluşturmak için.)
test
/ / komutlarını kullanmak için zaten başka talimatlarla belirlenmiş olan bayrakları kullanın" dedi ise cmp
, o zaman bu oldukça basit bir başlangıç x86 olurdu, fakat yine de bir artı değer kazanacaktı.
Bu x86'ya özgü değildir, ancak yaygın olarak uygulanabilir bir başlangıç montaj ipucudur. Bir süre döngüsünün en az bir kez çalışacağını biliyorsanız, döngüyü sonunda bir döngü döngüsü olarak yeniden yazarken, döngü 2 bayt atlama komutunu kaydeder. Özel bir durumda bile kullanabilirsiniz loop
.
do{}while()
montajda doğal döngü deyiminin neden olduğunu açıklar (özellikle verimlilik için). Ayrıca 2 bayt olduğunu Not jecxz
/ jrcxz
bir döngü ile çok iyi çalışır önce loop
"verimli" dava "sıfır kere çalıştırmak için ihtiyaçları" işlemek için (nadir CPU'lar üzerinde nerede loop
yavaş değil). jecxz
Ayrıca , altta bir a ile uygulamak için döngünün içinde kullanılabilirwhile(ecx){}
jmp
.
Sistem V x86 yığınını kullanır ve Sistem V X86-64 kullanır rdi
, rsi
, rdx
, rcx
, vb giriş parametreleri için ve rax
dönüş değeri olarak, ancak kendi çağrı kuralı kullanmak kesinlikle makul. __fastcall kullanır ecx
ve edx
girdi parametreleri olarak ve diğer derleyiciler / işletim sistemleri kendi kurallarını kullanır . Yığını kullanın ve uygun olduğunda giriş / çıkış olarak kaydedenleri kullanın.
Örnek: Tekrarlayan bayt sayacı , 1 baytlık bir çözüm için akıllı bir çağrı kuralı kullanarak.
Meta: kayıtları girdi Yazma , kayıtlar çıktı Yazma
Diğer kaynaklar: Agner Fog'un çağrı sözleşmeleriyle ilgili notları
int 0x80
, bir demet kurulum gerektirmesi.
int 0x80
32-bit kodda veya syscall
64-bit kodda çağırmak sys_write
, tek iyi yoldur. Extreme Fibonacci için kullandım . 64 bit kodunda __NR_write = 1 = STDOUT_FILENO
, böylece yapabilirsiniz mov eax, edi
. Veya EAX'in üst baytı sıfırsa, mov al, 4
32 bit kodunda. Ayrıca call printf
veya puts
sanırım "Linux + glibc için x86 asm" cevabı yazabilirsiniz. PLT veya GOT giriş alanını veya kütüphane kodunu kendisinin saymamasının makul olduğunu düşünüyorum.
char*buf
ve bunun içinde dizgiyi el ile biçimlendirmesi ile üretmesi için daha fazla eğimli olurdum . örn. bunun gibi (garip bir şekilde hız için optimize edilmiş) asm FizzBuzz , burada dizge verilerini kayıt altına aldım ve sonra saklıyorummov
çünkü dizgiler kısa ve sabit uzunlukluydu.
CMOVcc
ve kümeler kullanSETcc
Bu, kendime daha fazla hatırlatıyor, ancak koşullu küme yönergeleri var ve işlemcilere P6 (Pentium Pro) veya daha yenisinde koşullu hareket yönergeleri var. EFLAGS'ta ayarlanan bayraklardan bir veya daha fazlasına dayanan birçok talimat vardır.
cmov
2 baytlık bir opcode ( 0F 4x +ModR/M
) vardır, bu yüzden minimum 3 bayt. Ancak kaynak r / m32'dir, bu nedenle şartlı olarak 3 bayt yükleyebilirsiniz. Dallanma dışında, setcc
daha fazla durumda yararlıdır cmovcc
. Yine de, sadece temel 386 talimatlarını değil, tüm talimat setini göz önünde bulundurun. (Her ne kadar SSE2 ve BMI / BMI2 komutları nadiren faydalı olacak kadar büyük olsalar da. rorx eax, ecx, 32
6 bayt, mov + ror'dan daha uzun . Her ne kadar POPCNT veya PDEP çok fazla tasarruf yapmazsa golf için değil performans için güzel)
setcc
.
jmp
içine düzenleyerek bayt eğer / ise oldukça eğer / o / else dahaBu kesinlikle çok basit, sadece bunu golf oynarken düşünecek bir şey olarak göndereceğimi düşündüm. Örnek olarak, onaltılık bir rakam karakterinin kodunu çözmek için aşağıdaki basit kodu göz önünde bulundurun:
cmp $'A', %al
jae .Lletter
sub $'0', %al
jmp .Lprocess
.Lletter:
sub $('A'-10), %al
.Lprocess:
movzbl %al, %eax
...
Bu, bir "o zaman" davasının "başka" bir duruma girmesine izin verilerek iki byte kısaltılabilir:
cmp $'A', %al
jb .digit
sub $('A'-'0'-10), %eax
.digit:
sub $'0', %eax
movzbl %al, %eax
...
sub
bir durum için kritik yoldaki ekstra gecikme süresi döngü taşınan bir bağımlılık zincirinin parçası olmadığında (4 bitlik parçalar birleştirilene kadar her giriş hanesinin bağımsız olduğu durumlarda olduğu gibi) genellikle normalde yaparsınız. ). Ama yine de + 1 sanırım. BTW, örneğinizin ayrı bir cevapsız optimizasyonu var: movzx
yine de bir sonuca ihtiyaç sub $imm, %al
duyacaksanız, modrm olmayan 2 bayt kodlamanın avantajlarından yararlanmak için EAX kullanmayın op $imm, %al
.
cmp
yaparak ortadan kaldırabilirsiniz sub $'A'-10, %al
; jae .was_alpha
; add $('A'-10)-'0'
. (Sanırım mantık hakkım var). Not 'A'-10 > '9'
böylece belirsizlik olmaması. Bir harf için düzeltmeyi çıkarmak ondalık basamağı sarar. Bu nedenle, girdilerimizin tıpkı sizinki gibi geçerli altıgen olduğunu kabul edersek bu güvenlidir.
Ardışık nesneleri esi olarak esp ayarlayarak ve lodsd / xchg reg, eax dizisini gerçekleştirerek yığından alabilirsiniz.
pop eax
/ pop edx
/ ... ' dan daha iyi ? Onları yığında bırakmanız gerekiyorsa, push
ESP'yi geri yüklemek için geri döndürebilirsiniz, yine de nesne başına hala 2 bayt mov esi,esp
. Yoksa 64-bit kodundaki 4-byte'lık nesneler için 8 byte'ı pop
alabildin mi? BTW, Extreme Fibonaccipop
lodsd
64 bitlik bir kayıt defterini kopyalamak için push rcx
; pop rdx
3 bayt yerine mov
.
Varsayılan push / pop işlenen boyutu, bir REX ön ekine gerek olmadan 64-bit'tir.
51 push rcx
5a pop rdx
vs.
48 89 ca mov rdx,rcx
(İşlenen boyutu öneki, push / pop boyutunu 16 bit olarak geçersiz kılabilir, ancak 32 bit push / pop iş boyutu, REX.W = 0 olsa bile 64 bit modunda kodlanamaz.)
Kayıtlardan biri veya her ikisi de r8
.. ise r15
, mov
push ve / veya pop bir REX ön ekine ihtiyaç duyacağından kullanın. En kötü durum, her ikisinin de REX öneklerine ihtiyaç duyması durumunda kaybeder. Belli ki kod r8 de zaten r8..r15 kaçınmalısınız.
Bu NASM makrosu ile geliştirirken kaynağınızı daha okunaklı tutabilirsiniz . Sadece RSP'nin altındaki 8 byte'a çıktığını unutmayın. (X86-64 Sistem V'deki kırmızı bölgede). Ancak normal şartlar altında, 64-bit mov r64,r64
veyamov 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
Örnekler:
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
Bazen EAX veya Rax içine bir değer almak gerekir ve eski kopyasını muhafaza umurumda değil, çünkü örneğin bir parçasıdır. push / pop aslında değiş tokuş yapmanıza yardımcı olmuyor.
push 200; pop edx
başlatmak için: başlatma için örneğin –3 bayt kullanın .