x86 32 bit makine kodu işlevi, 42 41 bayt
Şu anda en kısa golf dili olmayan cevap, @ streetster'in q / kdb + değerinden 1B daha kısa .
Truthy için 0, falsy için sıfır olmayan: 41 40 bayt. (genel olarak 32 bit için 1 bayt, 64 bit için 2 bayt kaydeder).
Örtük uzunluklu dizelerle (0-sonlandırılmış C tarzı): 45 44 bayt
x86-64 makine kodu (x32 ABI gibi 32 bit işaretçilerle): 44 43 bayt .
Örtük uzunluklu dizelerle x86-64, hala 46 bayt (vardiya / maske bitmap stratejisi artık eşit değil).
Bu C imzalı bir fonksiyondur _Bool dennis_like(size_t ecx, const char *esi)
. Çağıran kural biraz standart dışıdır, MS vectorcall / fastcall'a yakındır, fakat farklı argüman kayıtlıdır: ESI'de dize ve ECX'de uzunluk. Sadece arg-reg'lerini ve EDX'ini engelliyor. AL, çöpleri tutan yüksek baytlarla (SysV x86 ve x32 ABI'lerin izin verdiği gibi) geri dönüş değerini tutar. IDK, MS'in ABI'lerinin bool veya dar tamsayılar döndürürken yüksek çöp hakkında söylediklerini belirtir.)
Algoritmanın açıklaması :
Girdi dizgisini gezdirin, yığında bir boolean dizisine filtreleyin ve sınıflandırın: Her bayt için, alfabetik bir karakter olup olmadığını kontrol edin (değilse, sonraki karaktere devam edin) ve onu 0-25 (AZ) arasında bir tam sayıya dönüştürün . Ünlü bir bit eşlemini kontrol etmek için bu 0-25 tamsayısını kullanın = 0 / ünsüz = 1. (Bitmap, 32 bitlik bir anlık sabit olarak bir kayıt defterine yüklenir). Bitmap sonucuna göre yığının üstüne 0 veya 0xFF (aslında en üstteki 3 baytta çöp olabilecek 32 bitlik bir öğenin düşük baytında) itin.
İlk döngü 0 veya 0xFF dizisi oluşturur (dword öğelerinde çöple doldurulmuş). Normal palindromu, işaretçiler ortasından geçtiğinde duran (ya da tek sayıda alfabetik karakter varsa, ikisi de aynı öğeye işaret ettiğinde) duran ikinci bir döngü ile kontrol edin. Yukarı doğru hareket eden işaretçi yığın işaretçisidir ve + artışını yüklemek için POP kullanıyoruz. Bu döngüdeki compare / setcc yerine, yalnızca iki olası değer olduğundan XOR'u aynı / farklı algılamak için kullanabiliriz. Eşleşmeyen herhangi bir öğe bulup bulamadığımızı (OR ile) biriktirebiliriz, ancak XOR tarafından belirlenen bayrakların ilk çıkardığı dal en azından iyidir.
İkinci döngünün byte
işlemsel boyut kullandığına dikkat edin , bu nedenle ilk döngünün her bir dizi öğesinin düşük baytının dışında ne kadar çöp bıraktığını önemsemez.
Belgelendirilmemiş salc
talimatı , AL'yi CF'den ayarlamak için olduğu gibi kullanır sbb al,al
. Her Intel işlemcisinde (64 bit modu hariç), Knight's Landing! Agner Fog bunun için tüm AMD işlemcilerinde (Ryzen dahil) zamanlamaları listeliyor , bu nedenle x86 satıcıları 8086'dan beri bu opcode alan baytını bağlamakta ısrar ediyorlarsa, bundan faydalanabiliriz.
İlginç püf noktaları:
- birleştirilmiş isalpha () ve toupper () için işaretsiz karşılaştırmalı hile ve sıfır, baytı doldurmak için baytı doldurarak aşağıdakileri ayarlar:
- bir kayıt anında bitmap
bt
, bazı güzel derleyici çıkışı esinlenerekswitch
.
- Bir döngü içinde push ile istif üzerinde değişken boyutlu bir dizi oluşturma. (Asm için standart, ancak kapalı uzunluklu dize sürümü için C ile yapabileceğiniz bir şey değil). Her giriş karakteri için 4 bayt yığın alanı kullanır, ancak en az 1 bayttan daha fazla golf oynamayı önler
stosb
.
- Boolean dizisindeki cmp / setne yerine, XOR doğrudan bir doğruluk değeri elde etmek için bir araya gelir. (
cmp
/ salc
bir seçenek değildir, çünkü salc
yalnızca CF için çalışır ve 0xFF-0 CF ayarlamaz. sete
3 bayttır, ancak inc
2 bayt net bir ücret karşılığında döngünün dışından kaçınır (64 bit modunda 1) )) vs. xor ve döngüde inc ile sabitleme.
; explicit-length version: input string in ESI, byte count in ECX
08048060 <dennis_like>:
8048060: 55 push ebp
8048061: 89 e5 mov ebp,esp ; a stack frame lets us restore esp with LEAVE (1B)
8048063: ba ee be ef 03 mov edx,0x3efbeee ; consonant bitmap
08048068 <dennis_like.filter_loop>:
8048068: ac lods al,BYTE PTR ds:[esi]
8048069: 24 5f and al,0x5f ; uppercase
804806b: 2c 41 sub al,0x41 ; range-shift to 0..25
804806d: 3c 19 cmp al,0x19 ; reject non-letters
804806f: 77 05 ja 8048076 <dennis_like.non_alpha>
8048071: 0f a3 c2 bt edx,eax # AL = 0..25 = position in alphabet
8048074: d6 SALC ; set AL=0 or 0xFF from carry. Undocumented insn, but widely supported
8048075: 50 push eax
08048076 <dennis_like.non_alpha>:
8048076: e2 f0 loop 8048068 <dennis_like.filter_loop> # ecx = remaining string bytes
; end of first loop
8048078: 89 ee mov esi,ebp ; ebp = one-past-the-top of the bool array
0804807a <dennis_like.palindrome_loop>:
804807a: 58 pop eax ; read from the bottom
804807b: 83 ee 04 sub esi,0x4
804807e: 32 06 xor al,BYTE PTR [esi]
8048080: 75 04 jne 8048086 <dennis_like.non_palindrome>
8048082: 39 e6 cmp esi,esp ; until the pointers meet or cross in the middle
8048084: 77 f4 ja 804807a <dennis_like.palindrome_loop>
08048086 <dennis_like.non_palindrome>:
; jump or fall-through to here with al holding an inverted boolean
8048086: 40 inc eax
8048087: c9 leave
8048088: c3 ret
;; 0x89 - 0x60 = 41 bytes
Bu muhtemelen aynı zamanda en hızlı cevaplardan biridir, çünkü golf oynamaların hiçbiri gerçekten çok fazla acı vermez, en azından 4x bellek kullanımının çok fazla önbellek kaybına neden olmadığı birkaç bin karakterin altındaki karakter dizileri için. (Ayrıca, tüm karakterlerin üzerinden geçmeden önce Dennis'e benzeyen dizgiler için erken çıkış yapan cevapları kaybedebilir.) Birçok CPU'dan salc
daha yavaş setcc
(örneğin Skylake'de 1 veya 3); bt/salc
hala bir dize arama veya regex maçtan daha hızlı. Ve ek yükü yok, bu yüzden kısa dizeleri için son derece ucuz.
Anında bir geçişle yapmak, yukarı ve aşağı yönler için sınıflandırma kodunu tekrarlamak anlamına gelir. Bu daha hızlı ancak daha büyük kod boyutunda olurdu. (Elbette hızlı istiyorsanız, SSE2 veya AVX2 ile bir seferde 16 veya 32 karakter yapabilirsiniz, yine de hile işaretli aralığın altına geçerek karşılaştırma hilesini kullanabilirsiniz).
Bu işlevi bir cmdline argümanıyla çağırmak için programı (ia32 veya x32 Linux için) test edin ve status = return değeriyle çıkın. int80h.org'danstrlen
uygulama .
; build with the same %define macros as the source below (so this uses 32-bit regs in 32-bit mode)
global _start
_start:
;%define PTRSIZE 4 ; true for x32 and 32-bit mode.
mov esi, [rsp+4 + 4*1] ; esi = argv[1]
;mov rsi, [rsp+8 + 8*1] ; rsi = argv[1] ; For regular x86-64 (not x32)
%if IMPLICIT_LENGTH == 0
; strlen(esi)
mov rdi, rsi
mov rcx, -1
xor eax, eax
repne scasb ; rcx = -strlen - 2
not rcx
dec rcx
%endif
mov eax, 0xFFFFAEBB ; make sure the function works with garbage in EAX
call dennis_like
;; use the 32-bit ABI _exit syscall, even in x32 code for simplicity
mov ebx, eax
mov eax, 1
int 0x80 ; _exit( dennis_like(argv[1]) )
;; movzx edi, al ; actually mov edi,eax is fine here, too
;; mov eax,231 ; 64-bit ABI exit_group( same thing )
;; syscall
Bu işlevin 64 bit sürümü, sbb eax,eax
bunun yerine 3 yerine yalnızca 2 bayt olan kullanılabilir setc al
. Aynı zamanda dec
ya not
da sonunda fazladan bir bayta ihtiyaç duyacaktır (çünkü sadece 32-bit 1 bayt inc / dec r32'ye sahiptir). X32 ABI (uzun modda 32 bit işaretçiler) kullanarak, işaretçileri kopyalayıp karşılaştırsak bile REX öneklerinden kaçınabiliriz.
setc [rdi]
doğrudan belleğe yazabilir, ancak ECX byte yığın alanını saklamak, kaydettiğinden daha fazla kod boyutundadır. (Çıktı dizisinde ilerlememiz gerekiyor. [rdi+rcx]
Adresleme modu için bir bayt daha alıyor, ancak gerçekten filtrelenmiş karakterler için güncelleme yapmayan bir sayaca ihtiyacımız var, bu yüzden bundan daha kötü olacak.)
Bu, koşullu YASM / NASM kaynağıdır %if
. Bu inşa edilebilir -felf32
(32-bit kod) veya -felfx32
(x32 ABI'lı 64 bit kod) ve örtülü veya açık uzunluğu . 4 sürümü de test ettim. NASM / YASM kaynağından statik bir ikili oluşturmak için bir komut dosyası için bu cevaba bakın .
64 bit sürümünü x32 ABI desteği olmayan bir makinede test etmek için işaretçi reglerini 64 bit olarak değiştirebilirsiniz. (Ardından, REX.W = 1 öneklerinin sayısını (0x48 bytes) sayıdan çıkarın. Bu durumda, 4 komutun 64-bit regs üzerinde çalışması için REX önekleri gerekir). Ya da sadece rsp
4G adres boşluğundaki ve ile giriş işaretçisini arayın .
%define IMPLICIT_LENGTH 0
; This source can be built as x32, or as plain old 32-bit mode
; x32 needs to push 64-bit regs, and using them in addressing modes avoids address-size prefixes
; 32-bit code needs to use the 32-bit names everywhere
;%if __BITS__ != 32 ; NASM-only
%ifidn __OUTPUT_FORMAT__, elfx32
%define CPUMODE 64
%define STACKWIDTH 8 ; push / pop 8 bytes
%else
%define CPUMODE 32
%define STACKWIDTH 4 ; push / pop 4 bytes
%define rax eax
%define rcx ecx
%define rsi esi
%define rdi edi
%define rbp ebp
%define rsp esp
%endif
; A regular x86-64 version needs 4 REX prefixes to handle 64-bit pointers
; I haven't cluttered the source with that, but I guess stuff like %define ebp rbp would do the trick.
;; Calling convention similar to SysV x32, or to MS vectorcall, but with different arg regs
;; _Bool dennis_like_implicit(const char *esi)
;; _Bool dennis_like_explicit(size_t ecx, const char *esi)
global dennis_like
dennis_like:
; We want to restore esp later, so make a stack frame for LEAVE
push rbp
mov ebp, esp ; enter 0,0 is 4 bytes. Only saves bytes if we had a fixed-size allocation to do.
; ZYXWVUTSRQPONMLKJIHGFEDCBA
mov edx, 11111011111011111011101110b ; consonant/vowel bitmap for use with bt
;;; assume that len >= 1
%if IMPLICIT_LENGTH
lodsb ; pipelining the loop is 1B shorter than jmp .non_alpha
.filter_loop:
%else
.filter_loop:
lodsb
%endif
and al, 0x7F ^ 0x20 ; force ASCII to uppercase.
sub al, 'A' ; range-shift to 'A' = 0
cmp al, 'Z'-'A' ; if al was less than 'A', it will be a large unsigned number
ja .non_alpha
;; AL = position in alphabet (0-25)
bt edx, eax ; 3B
%if CPUMODE == 32
salc ; 1B only sets AL = 0 or 0xFF. Not available in 64-bit mode
%else
sbb eax, eax ; 2B eax = 0 or -1, according to CF.
%endif
push rax
.non_alpha:
%if IMPLICIT_LENGTH
lodsb
test al,al
jnz .filter_loop
%else
loop .filter_loop
%endif
; al = potentially garbage if the last char was non-alpha
; esp = bottom of bool array
mov esi, ebp ; ebp = one-past-the-top of the bool array
.palindrome_loop:
pop rax
sub esi, STACKWIDTH
xor al, [rsi] ; al = (arr[up] != arr[--down]). 8-bit operand-size so flags are set from the non-garbage
jnz .non_palindrome
cmp esi, esp
ja .palindrome_loop
.non_palindrome: ; we jump here with al=1 if we found a difference, or drop out of the loop with al=0 for no diff
inc eax ;; AL transforms 0 -> 1 or 0xFF -> 0.
leave
ret ; return value in AL. high bytes of EAX are allowed to contain garbage.
Df (kontrol yönü bayrağı ile karıştırmasını baktı lodsd
/ scasd
böyle devam eder), ama sadece bir kazan olarak görünmüyordu. Her zamanki ABI'ler, DF'nin işlev girişi ve çıkışı sırasında temizlenmesini gerektirir. Girişte silinmiş kabul edilir, ancak çıkışta ayarlanmış bırakılmak, hile yapmak IMO olacaktır. 3-bayttan kaçınmak için LODSD / SCASD kullanmak sub esi, 4
, özellikle de yüksek çöp bulunmadığı durumlarda kullanmak iyi olurdu .
Alternatif bitmap stratejisi (x86-64 tam uzunlukta dizeler için)
Görünüşe göre, bu herhangi bir bayt tasarruf etmiyor, çünkü bt r32,r32
hala bit indeksinde yüksek çöplerle çalışıyor. Sadece bu şekilde belgelenmemiş shr
.
Bitin bt / sbb
CF'ye girip çıkması yerine , bitmap'ten istediğimiz bit'i izole etmek için bir shift / mask kullanın.
%if IMPLICIT_LENGTH && CPUMODE == 64
; incompatible with LOOP for explicit-length, both need ECX. In that case, bt/sbb is best
xchg eax, ecx
mov eax, 11111011111011111011101110b ; not hoisted out of the loop
shr eax, cl
and al, 1
%else
bt edx, eax
sbb eax, eax
%endif
push rax
Bu, sonunda AL'de 0/1 ürettiğinden (0 / 0xFF yerine), fonksiyonun sonunda xor al, 1
(2B) yerine (x86-64'te 2B) yerine dönüş değerinin gerekli inversiyonunu yapabiliriz dec eax
. Yine de uygun bool
/_Bool
dönüş değeri üretir .
Bu, EAX'in yüksek baytlarını sıfırlamaktan kaçınmak suretiyle, örtük uzunlukta dizelerle x86-64 için 1B tasarruf etmek için kullanılır. ( and eax, 0x7F ^ 0x20
3-byte ile eax geri kalanını büyük-küçük harf zorlamak ve sıfırlamak için and r32,imm8
kullanıyordum. Ama şimdi yaptığım gibi, 8086 komutlarının çoğunda olduğu gibi 2-by-AL kodlu hemen kullanıyorum. için sub
ve cmp
.)
32 bit modunda bt
/ salc
modunda kaybeder ve açık uzunluklu dizelerin sayım için ECX'e ihtiyacı vardır, bu nedenle orada da çalışmaz.
Ama sonra yanıldığımı farkettim: bt edx, eax
hala eax'ta yüksek çöplerle çalışıyor. Görünüşe maskeleri vardiya saymak aynı şekilde shr r32, cl
yok (cl düşük 5 bit sadece bakarak). Bu, bt [mem], reg
adresleme modu / büyüklüğü tarafından başvurulan belleğin dışına erişerek, onu bir bit karesi olarak algılayandan farklıdır . (Çılgın CISC ...)
Intel'in ayarlanan ref el kitabı maskelemeyi belgelemiyor, bu yüzden belki de Intel'in koruduğu belgelenmemiş davranış. (Bu tip şeyler nadir değildir. bsf dst, src
Src = 0 daima dst değiştirilmemiş bırakır ile, söz konusu durumda tanımlanmamış bir değer tutan dst bırakmak belgelenir bile. AMD aslında src = 0 davranışını belgeler.) Ben Skylake ve Core2 üzerinde test, ve bt
sürüm, EAX’de AL dışındaki sıfır olmayan çöplerle çalışır.
Burada düzgün bir numara, xchg eax,ecx
CL'yi saymak için (1 byte) kullanıyor . Ne yazık ki, BMI2 shrx eax, edx, eax
5 byte, sadece 2 byte shr eax, cl
. Kullanımı bextr
2 bayt mov ah,1
(ayıklanacak bit sayısı için) gerektirir, bu yüzden yine SHRX + AND gibi 5 + 2 bayttır.
Kaynak kodu %if
koşullu ekledikten sonra oldukça karışık olmuştur . İşte x32 örtük uzunlukta dizgelerin (bitmap için alternatif bir strateji kullanarak, bu yüzden hala 46 bayt) sökülmesi .
Açık uzunluklu sürümden temel fark ilk döngüdedir. lods
Döngünün en üstünde bir tane yerine ondan önce ve en altında nasıl olduğuna dikkat edin .
; 64-bit implicit-length version using the alternate bitmap strategy
00400060 <dennis_like>:
400060: 55 push rbp
400061: 89 e5 mov ebp,esp
400063: ac lods al,BYTE PTR ds:[rsi]
00400064 <dennis_like.filter_loop>:
400064: 24 5f and al,0x5f
400066: 2c 41 sub al,0x41
400068: 3c 19 cmp al,0x19
40006a: 77 0b ja 400077 <dennis_like.non_alpha>
40006c: 91 xchg ecx,eax
40006d: b8 ee be ef 03 mov eax,0x3efbeee ; inside the loop since SHR destroys it
400072: d3 e8 shr eax,cl
400074: 24 01 and al,0x1
400076: 50 push rax
00400077 <dennis_like.non_alpha>:
400077: ac lods al,BYTE PTR ds:[rsi]
400078: 84 c0 test al,al
40007a: 75 e8 jne 400064 <dennis_like.filter_loop>
40007c: 89 ee mov esi,ebp
0040007e <dennis_like.palindrome_loop>:
40007e: 58 pop rax
40007f: 83 ee 08 sub esi,0x8
400082: 32 06 xor al,BYTE PTR [rsi]
400084: 75 04 jne 40008a <dennis_like.non_palindrome>
400086: 39 e6 cmp esi,esp
400088: 77 f4 ja 40007e <dennis_like.palindrome_loop>
0040008a <dennis_like.non_palindrome>:
40008a: ff c8 dec eax ; invert the 0 / non-zero status of AL. xor al,1 works too, and produces a proper bool.
40008c: c9 leave
40008d: c3 ret
0x8e - 0x60 = 0x2e = 46 bytes