X86 / x64 makine kodunda golf oynamak için ipuçları


27

Böyle bir soru olmadığını fark ettim, işte burada:

Makine kodunda golf oynamak için genel ipuçlarınız var mı? Bahşiş, yalnızca belirli bir ortam veya arama sözleşmesi için geçerliyse, lütfen cevabınızı belirtin.

Lütfen cevap başına sadece bir ipucu ( buraya bakınız ).

Yanıtlar:


11

mov-sıkı sabitler için pahalıdır

Bu 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.

Başlatılıyor eaxile 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

Başlatılıyor eaxile -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/ popdefault 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


Ayrıca, 0 dışında küçük (8 bit) bir değere sahip bir kayıt push 200; pop edxbaşlatmak için: başlatma için örneğin –3 bayt kullanın .
anatolyg

2
BTW -1'e bir kayıt başlatmak, kullanmak dec, örneğinxor eax, eax; dec eax
anatolyg

@anatolyg: 200 kötü bir örnektir, işaret genişletilmiş imm8'e uymuyor. Ama evet, push imm8/ pop reg3 bayt ve x86-64, 64 bit sabitleri için harika dec/ inc2 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
Peter Cordes

10

Birçok durumda, akümülatör tabanlı talimatlar (yani (R|E)AXhedef operand olarak geçenler ) genel durum talimatlarından 1 bayt daha kısadır; StackOverflow bu soruya bakın .


Normalde en faydalı olanlar, / / / al, imm8gibi 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, 0x20sub al, 'a'cmp al, 'z'-'a'ja .non_alphabeticallodsbstosballodsdtest al, 1setnz clop eax, imm32
Peter Cordes

8

İstediğiniz yere koymak için arama kuralınızı seçin.

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/raxzaten mevcut değilse geri dönüş değerini koyan bir sarmalayıcı yazabilirsiniz .


Makul olanın sınırları: Arayana makul olmayan bir yük getirmeyen herhangi bir şey:

  • ESP / RSP çağrı korumalı olmalıdır; diğer tamsayı regs adil bir oyundur. (RBP ve RBX, normal kurallarda genellikle çağrı korumalıdır, ancak ikisini de kesebilirsiniz.)
  • Herhangi bir kayıttaki herhangi bir argüman (RSP hariç) makul, ancak arayandan aynı argümanı birden fazla kayda kopyalamasını istemek doğru değildir.
  • Ç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 st0makul, ancak st3diğer x87 registerlarındaki çöplerle geri dönmek de mümkün değil. Arayan x87 yığınını temizlemek zorunda kalacaktı. st0Boş olmayan daha yüksek yığın kayıtlarına geri dönmek bile sorgulanabilir (birden fazla değer döndürmediğiniz sürece).

  • İşleviniz ile çağrılacak call, [rsp]dönüş adresiniz de öyle. Sen edebilirsiniz önlemek call/ retbağlantı kaydını gibi kullanarak x86 üzerinde lea rbx, [ret_addr]/ jmp functionile 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.
  • RSP'nin üzerindeki sınırsız hafızayı engellemek mantıklı değildir, ancak normal arama kurallarında yığında işlev hatalarının gizlenmesi yasaktır. x64 Windows, geri dönüş adresinin üzerinde 32 bayt gölge alanı gerektirirken, x86-64 System V size RSP'nin altında 128 byte kırmızı bölge verir, bu nedenle ikisi de makul olur. (Ya da çok daha büyük bir kırmızı bölge, özellikle de işlev yerine tek başına bir programda.)

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 xmm0için movlps [rdi], xmm0de garip bir çağrı kuralı olur.


FLAGS'ta bir boole döndürür (koşul kodları)

OS X sistem çağrıları bunu yapar ( CF=0hata 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 jneeşit olmadıklarında alınacaktır).


Dar charya 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 movzxveya 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_tveya int64_tprototipinizde 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.

longWindows 64-bit ABI ve Linux x32 ABI'de yalnızca 32 bitlik bir tür olduğuna dikkat edin ; uint64_tbelirsiz ve yazmaktan daha kısa unsigned long long.


Mevcut arama kuralları:

  • Windows 32-bit __fastcall, zaten başka bir cevap tarafından önerildi : tamsayı args ecxve 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 memcpyya da memset'e rep movsbkolay ş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/ stosdiçinde kullanılıyorsa rcx( looptalimat 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 rax2 byte ve eşdeğerdir mov rax,rspyine böylece, kopyalama 2 bayt tam 64 bit kayıtlarını.


Zorluklar bir dizi döndürmek istediğinde, yığına geri dönmenin makul olduğunu düşünüyor musunuz? Bence bir derleyici değeri döndürürken derleyiciler ne yapacağını düşünüyorum.
qwr

@ qwr: hayır, genel çağrı yapma kuralları, dönüş değerine gizli bir işaretçi iletir. (Bazı sözleşmeler, kayıtlardaki küçük yapıları geçer / verir). C / C ++ başlık altında değeriyle yapı dönen ve sonunu bkz nesneler montaj düzeyinde x86 çalışmak nasıl? . Bu Not geçen (yapılar içinde) diziler X86-64 SysV için yığın üzerine kopyalamak etmez tür C11 veri türü AMD64 ABI göre bir dizidir ne , ancak Windows 64, bir const olmayan işaretçi geçirir.
Peter Cordes

Peki makul hakkında ne düşünüyorsunuz? Bu kural uyarınca x86'yı kodluyorsunuz codegolf.meta.stackexchange.com/a/8507/17360
qwr

1
@ qwr: x86 "yığın tabanlı dil" değil. x86, RAM'lı bir kayıt makinesidir , yığın makinesi değil . Bir yığın makinesi ters-cila gösterimi gibidir, x87 kayıtları gibi. fld / fld / faddp. x86'nın çağrı yığını bu modele uymuyor: tüm normal çağrı kuralları RSP'yi değiştirilmemiş halde bırakıyor veya argümanları açıyor 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.
Peter Cordes

Çağrı, çağrılan işlevin ardından jmp yığınındaki çağrının ardından komutun adresini itin; ret yığınını ve jmp adresini bu adrese pop
RosLuP

7

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/decbir kayıt (8 bit dışında): inc eax/ dec ebp. (X86-64 değil: 0x4xopcode baytları REX önekleri olarak yeniden düzenlenmiştir inc r/m32, tek kodlamadır.)

    8 bit inc bl, inc r/m8opcode + ModR / M operand kodlamasını kullanarak 2 bayttır . Yani kullanmak inc ebxartıma blgü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, scasbbir 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 xchgbir yönergelere cdq/ idivdöngü 8 byte GCD'nın için bir bağımlılığı dahil talimatları çoğu tek baytlık vardır inc ecx/ loopyerine 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: cltqvs.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 += 4olmadığı 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 rdibayt 2'dir, ancak mov rdi, rspbir REX ön ekine ve 3 bayttır.

xlatbvar, 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/ sahfnadiren kullanışlıdır. Sen could lahf / and ah, 1alternatif olarak setc ah, ancak genellikle yararlıdır değil.

Ve özellikle CF için, bayrakları etkilemeden etkili sbb eax,eaxbir ş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,eaxolarak 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, 02 bayt içinde AL ekleyebilirsiniz, ve daha önce belgelenmemiş SALC bahsetti.

std/ cldnadiren 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 decbir işaretçi ve a movveya 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 .)lodsbstosbstdcldlodsstoscld


8086 geçmişi: bu kodlamalar neden var

Özgün 8086 yılında AX çok özel: talimatlar gibi lodsb/ stosb, cbw, mul/ divve 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/ movzxtaşıma + işaret genişletme veya 2 ve 3 işlemsel gibi eklemelere imul cx, bx, 1234sahip 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,imm8veya AX,imm16veya (32 bit modda) EAX,imm32.

Ancak bunun için özel bir durum yok EAX,imm8, bu yüzden normal ModR / M kodlaması add eax,4daha 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/wEAX 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).


cdqbirçok durumda divsıfırlanan ihtiyaçlar için kullanışlıdır edx.
qwr

1
@qwr: haklı olarak, temettüsünüzün 2 ^ 31’in altında olduğunu biliyorsanız (imzalı olarak değerlendirildiğinde olumsuz değildir) veya potansiyel olarak büyük bir değere ayarlamadan önce kullanırsanız, imzalanmamış cdqönce kötüye divkullanabilirsiniz eax. Normalde (dış kod golf dışında) kurulum için ve daha önce kullanacağınızcdqidivxor edx,edxdiv
Peter Cordes

5

fastcallSözleşmeleri kullan

x86 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 ecxve edx. Eğer fonksiyonunuz 3 parametre içeriyorsa, bunu 64-bit bir platformda uygulamayı düşünebilirsiniz.

fastcallKongre 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   

Ya da tamamen özel bir arama kuralı kullanın , çünkü tamamen AS'den yazıyorsunuz, mutlaka C'den çağrılacak kod yazmıyorsunuzdur.
Peter Cordes,

5

128 yerine -128 çıkartın

0100 81C38000      ADD     BX,0080
0104 83EB80        SUB     BX,-80

Samely, 128 çıkarma yerine -128 ekleyin


1
Bu aynı zamanda tabii ki, diğer yöne çalışır: dönüm ilgili optimizasyonu yapmak da derleyiciler Bu optimizasyonu biliyorum ve: yerine alt 128. Eğlence gerçeğin -128 eklemek < 128içine <= 127yö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 .
Peter Cordes

4

Ile 3 sıfır oluşturun mul(sonra inc/ decsı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 LOOPdö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.


1
Bu biraz kafa karıştırıcı. Genişletebilir misin?
Hayır

@HayırOnlar EAX ve EDX de dahil olmak üzere 0'a üç tane kayıt yapmak istediğine inanıyorum.
NieDzejkob

4

CPU kayıtları ve bayrakları bilinen başlangıç ​​durumlarındadır

İş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


1
Code Golf kuralları, kodunuzun en az bir uygulama üzerinde çalışması gerektiğini söylüyor. Linux, i386 ve x86-64 System V ABI belgeleri girişte "tanımsız" olduklarını söylese de, yeni bir kullanıcı alanı işlemine girmeden önce tüm kayıtları (RSP hariç) sıfırlamayı ve istiflemeyi seçer _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 _startve yapar kayıtlarında izin çöp, ancak statik sadece koddur.)
Peter Cordes

3

1 eklemek veya çıkarmak için, bir bayt incveya decçok baytlı toplama ve alt talimatlardan daha küçük olan talimatları kullanın.


32 bit kipinde, inc/dec r32op kodda kodlanmış kayıt numarasıyla 1 bayta sahip olduğuna dikkat edin. Öyleyse inc ebx1 bayt, ancak inc bl2'dir add bl, 1. Tabii ki hala küçüktür al. Ayrıca CF değiştirilmemiş inc/ decbırakın, ancak diğer bayrakları güncelleyin.
Peter Cordes

1
+2 2 ve -2 X 86
l4m2

3

lea matematik için

Muhtemelen bu, x86 hakkında öğrenilen ilk şeylerden biri, ancak burada hatırlatma olarak bırakıyorum. lea2, 3, 4, 5, 8 veya 9 ile çarpma yapmak ve bir ofset eklemek için kullanılabilir.

Örneğin, ebx = 9*eax + 3tek 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 leagibi matematik yapmak için de kullanılabilir ebx = edx + 8*eax + 3.


1
Belki 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).
Peter Cordes

3

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 ECXve jnz <label>, ve lodsbdaha küçük olan mov al,[esi]ve inc si.


2

mov küçük, uygun olduğunda daha düşük kayıtlara başlar

Bir 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

İmm8 ila sıfır üst bit için push/ kullanınpop

Peter Cordes'e teşekkür ederim. xor/ mov4 bayt, ancak push/ popyalnızca 3!

6a 0a                   push   $0xa
58                      pop    %eax

mov al, 0xaSıfır genişletilmiş tam reg gerekmiyorsa iyidir. Ama yaparsanız, xor / mov, imm8 / pop için veya leabilinen başka bir sabitten 4 bayt vs 3'tür . Bu arada yararlı olabilir ile mul4 bayt 3 sıfır kayıtları veya cdqsabitlerin çok gerekiyorsa olsa,.
Peter Cordes

Diğer kullanım durumu [0x80..0xFF], işaret uzatılmış imm8 olarak gösterilemeyen sabitler içindir. Ya da üst baytları zaten biliyorsanız, örneğin mov cl, 0x10bir looptalimattan sonra , çünkü zıplamamanın tek yolu, loopyapı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 -1024ebx'e ve kullanımları bl.
Peter Cordes

@PeterCordes Push / pop tekniğini
ekledim

Muhtemelen sabitler hakkında mevcut cevaba girmeliyim , anatolyg zaten bir yorumda bulundu . Bu cevabı düzenleyeceğim. IMO, daha fazla şeyler için 8 bitlik işlenen boyutu kullanmanızı önermek için bunu yeniden çalışmalısınız ( xchg eax, r32örneğin hariç ) mov bl, 10/ dec bl/ jnzböylece kodunuz RBX'in yüksek baytını önemsemez.
Peter Cordes

@PeterCordes hmm. 8 bitlik operandların ne zaman kullanılacağından hala emin değilim bu yüzden bu cevaba ne yazacağımdan emin değilim.
qwr

2

BAYRAK birçok talimatlar sonra ayarlanır

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.


Parite bayrağını ne zaman kullandın? Bunun sonucun 8 bitinin yatay xor olduğunu biliyorsunuz, değil mi? (Operand boyutundan bağımsız olarak, PF sadece düşük 8 bitten ayarlanır ; ayrıca bakınız ). Çift sayı / tek sayı değil; bunun için ZF'den sonra test al,1; genellikle bunu bedavaya alamazsın. (Ya da and al,1tek / çift oranına bağlı olarak 0/1 tamsayı oluşturmak için.)
Peter Cordes

Her neyse, eğer bu cevap " 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ı.
Peter Cordes

@PeterCordes Huh, parite bayrağını yanlış anlamış gibiydim. Hala diğer cevabım üzerinde çalışıyorum. Cevabı düzenleyeceğim. Ve muhtemelen söyleyebileceğiniz gibi, ben başlangıç ​​seviyesindeyim, bu yüzden temel ipuçları yardımcı olur.
qwr

2

Döngüler yerine do-while döngüsünü kullanın.

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.


2
İlgili: Neden döngüler her zaman böyle derlenir? 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/ jrcxzbir 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 loopyavaş değil). jecxzAyrıca , altta bir a ile uygulamak için döngünün içinde kullanılabilirwhile(ecx){}jmp .
Peter Cordes,

@PeterCordes bu çok iyi yazılmış bir cevaptır. Bir kod golf programında bir döngünün ortasına atlamak için bir kullanım bulmak isterim.
qwr

Kullan jmp ve girinti kullanın ... Döngü takip
RosLuP

2

Hangi arama kurallarına uygunsa kullanın

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 raxdönüş değeri olarak, ancak kendi çağrı kuralı kullanmak kesinlikle makul. __fastcall kullanır ecxve edxgirdi 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ı


1
Sonunda , bu sözleşmeye kendi çağrılarımı koyarak çağrı sözleşmeleri yapma ve makul olanla mantıksız olanı yazdım .
Peter Cordes

@PeterCordes ilgisiz, x86'da yazdırmanın en iyi yolu nedir? Şimdiye kadar baskı gerektiren zorluklardan kaçındım. DOS, G / Ç için yararlı kesintilere sahip görünüyor, ancak yalnızca 32/64 bit yanıtları yazmayı planlıyorum. Benim bildiğim tek yol int 0x80, bir demet kurulum gerektirmesi.
qwr

Evet, int 0x8032-bit kodda veya syscall64-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, 432 bit kodunda. Ayrıca call printfveya putssanı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.
Peter Cordes

1
Arayanın a'yı geçmesi char*bufve 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.
Peter Cordes

1

Koşullu hamle CMOVccve 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.


1
Dallanma genellikle daha küçük buldum. Doğal bir uyum olduğu bazı durumlar vardır, ancak cmov2 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, setccdaha 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, 326 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)
Peter Cordes

@PeterCordes teşekkürler, ben ekledim setcc.
qwr

1

Kaydet jmpiçine düzenleyerek bayt eğer / ise oldukça eğer / o / else daha

Bu 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
    ...

Bunu, performansı optimize ederken, özellikle subbir 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: movzxyine de bir sonuca ihtiyaç sub $imm, %alduyacaksanız, modrm olmayan 2 bayt kodlamanın avantajlarından yararlanmak için EAX kullanmayın op $imm, %al.
Peter Cordes

Ayrıca, cmpyaparak 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.
Peter Cordes

0

Ardışık nesneleri esi olarak esp ayarlayarak ve lodsd / xchg reg, eax dizisini gerçekleştirerek yığından alabilirsiniz.


Bu neden pop eax/ pop edx/ ... ' dan daha iyi ? Onları yığında bırakmanız gerekiyorsa, pushESP'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'ı popalabildin mi? BTW, Extreme Fibonaccipoplodsd
Peter Cordes

yedek bir kaydınız olmadıkça pop kullanılmasını engelleyen "lea esi, [esp + ret adres büyüklüğü]" den sonra daha doğru bir şekilde kullanışlıdır.
peter ferrie

Oh, işlevler için mi? Oldukça ender rastlanan kayıtlardan daha fazla sorun istersiniz ya da arayanın hepsini kayıtlara geçirmek yerine hafızada bir tane bırakmasını istersiniz. (Standart kayıt çağrı sözleşmelerinden birinin tam olarak uymaması durumunda, özel görüşme kurallarını kullanma konusunda yarı bitmiş bir cevabım var.)
Peter Cordes

fastcall yerine cdecl, parametreleri yığında bırakır ve birçok parametreye sahip olmak kolaydır. Örneğin, github.com/peterferrie/tinycrypt adresini ziyaret edin.
peter ferrie,

0

Codegolf ve ASM için: Talimatlar kullanın, sadece kayıtları kullanın, pop tuşuna basın, kayıt hafızasını veya hafızayı en aza indirin


0

64 bitlik bir kayıt defterini kopyalamak için push rcx; pop rdx3 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, movpush 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,r64veyamov 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)

xchgBazen 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.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.