x86 32-bit fungsi kode mesin, 42 41 byte
Saat ini jawaban non-golf-bahasa terpendek, 1B lebih pendek dari @ streetster's q / kdb + .
Dengan 0 untuk truey dan non-zero untuk falsy: 41 40 bytes. (secara umum, menyimpan 1 byte untuk 32-bit, 2 byte untuk 64-bit).
Dengan string implisit-panjang (gaya-C diakhiri): 45 44 byte
kode mesin x86-64 (dengan pointer 32-bit, seperti x32 ABI): 44 43 byte .
x86-64 dengan string implisit-panjang, masih 46 byte (strategi bitmap shift / mask adalah impas sekarang).
Ini adalah fungsi dengan C tanda tangan _Bool dennis_like(size_t ecx, const char *esi)
. Konvensi panggilan agak non-standar, dekat dengan MS vectorcall / fastcall tetapi dengan register arg yang berbeda: string dalam ESI dan panjang dalam ECX. Hanya clobbers arg-regs dan EDX-nya. AL memegang nilai balik, dengan byte tinggi yang menyimpan sampah (sebagaimana diizinkan oleh SysV x86 dan x32 ABI. IDK apa yang dikatakan ABI MS tentang sampah tinggi ketika mengembalikan bool atau integer sempit.)
Penjelasan algoritma :
Ulangi string input, memfilter dan mengklasifikasikan ke dalam array boolean pada stack: Untuk setiap byte, periksa apakah itu karakter alfabet (jika tidak, lanjutkan ke karakter berikutnya), dan ubah ke integer dari 0-25 (AZ) . Gunakan bilangan bulat 0-25 untuk memeriksa bitmap vow = 0 / konsonan = 1. (Bitmap dimuat ke dalam register sebagai konstanta langsung 32-bit). Dorong 0 atau 0xFF ke tumpukan sesuai dengan hasil bitmap (sebenarnya dalam byte rendah elemen 32-bit, yang mungkin memiliki sampah di 3 byte teratas).
Loop pertama menghasilkan array 0 atau 0xFF (dalam elemen kata yang diisi dengan sampah). Lakukan pemeriksaan palindrom yang biasa dengan loop kedua yang berhenti ketika pointer melintas di tengah (atau ketika keduanya menunjuk ke elemen yang sama jika ada sejumlah karakter alfabet yang aneh). Pointer yang bergerak ke atas adalah stack pointer, dan kami menggunakan POP untuk memuat + kenaikan. Daripada membandingkan / setcc dalam loop ini, kita bisa menggunakan XOR untuk mendeteksi yang sama / berbeda karena hanya ada dua nilai yang mungkin. Kami dapat mengakumulasikan (dengan OR) apakah kami menemukan elemen yang tidak cocok, tetapi cabang awal pada flag yang ditetapkan oleh XOR setidaknya sama baiknya.
Perhatikan bahwa loop kedua menggunakan byte
ukuran operan, jadi tidak peduli sampah apa yang ditinggalkan loop pertama di luar byte rendah dari setiap elemen array.
Ia menggunakan instruksi tidak berdokumensalc
untuk mengatur AL dari CF, dengan cara yang sama sbb al,al
akan. Ini didukung pada setiap CPU Intel (kecuali dalam mode 64-bit), bahkan Knight's Landing! Agner Fog mencantumkan penetapan waktu untuk itu pada semua CPU AMD juga (termasuk Ryzen), jadi jika vendor x86 bersikeras untuk mengikat byte ruang opcode itu sejak 8086, sebaiknya kita memanfaatkannya.
Trik menarik:
- trik unsigned-bandingkan untuk isalpha () dan toupper () yang dikombinasikan, dan zero-extends byte untuk mengisi eax, mengatur untuk:
- bitmap langsung dalam register untuk
bt
, terinspirasi oleh beberapa keluaran kompiler yang bagus untukswitch
.
- Membuat array berukuran variabel pada stack dengan mendorong dalam satu lingkaran. (Standar untuk asm, tetapi bukan sesuatu yang dapat Anda lakukan dengan C untuk versi string implisit-panjang). Ini menggunakan 4 byte ruang stack untuk setiap karakter input, tetapi menyimpan setidaknya 1 byte vs bermain golf yang optimal
stosb
.
- Alih-alih cmp / setne pada array boolean, XOR booleans bersama-sama untuk mendapatkan nilai kebenaran secara langsung. (
cmp
/ salc
bukan pilihan, karena salc
hanya berfungsi untuk CF, dan 0xFF-0 tidak mengatur CF sete
adalah 3 byte, tetapi akan menghindari inc
loop luar, dengan biaya bersih 2 byte (1 dalam mode 64-bit) )) vs. xor di loop dan memperbaikinya dengan inc.
; 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
Ini mungkin juga salah satu jawaban tercepat, karena tidak ada golf yang benar-benar sakit terlalu parah, setidaknya untuk string di bawah beberapa ribu karakter di mana penggunaan memori 4x tidak menyebabkan banyak cache-misses. (Mungkin juga kalah dari jawaban yang mengambil string awal untuk string yang tidak seperti Dennis sebelum mengulang semua karakter.) Lebih salc
lambat daripada setcc
pada banyak CPU (misalnya 3 uops vs 1 pada Skylake), tetapi bitmap memeriksa dengan bt/salc
masih lebih cepat dari pencarian string atau pertandingan-regex. Dan tidak ada overhead startup, jadi sangat murah untuk string pendek.
Melakukannya dalam satu pass on the fly berarti mengulang kode klasifikasi untuk arah naik dan turun. Itu akan lebih cepat tetapi ukuran kode lebih besar. (Tentu saja jika Anda ingin cepat, Anda dapat melakukan 16 atau 32 karakter sekaligus dengan SSE2 atau AVX2, masih menggunakan trik perbandingan dengan menggeser rentang ke bagian bawah rentang yang ditandatangani).
Uji program (untuk ia32 atau x32 Linux) untuk memanggil fungsi ini dengan argumen cmdline, dan keluar dengan status = nilai balik. strlen
implementasi dari int80h.org .
; 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
Versi 64-bit dari fungsi ini dapat digunakan sbb eax,eax
, yang hanya 2 byte, bukan 3 untuk setc al
. Itu juga akan membutuhkan byte tambahan untuk dec
atau not
di akhir (karena hanya 32-bit memiliki 1-byte inc / dec r32). Menggunakan ABI x32 (pointer 32-bit dalam mode panjang), kita masih dapat menghindari awalan REX meskipun kita menyalin dan membandingkan pointer.
setc [rdi]
dapat menulis langsung ke memori, tetapi memesan byte ruang stack ECX biaya lebih banyak ukuran kode daripada menghemat. (Dan kita perlu bergerak melalui array output. [rdi+rcx]
Membutuhkan satu byte tambahan untuk mode pengalamatan, tetapi sebenarnya kita membutuhkan penghitung yang tidak memperbarui untuk karakter yang difilter sehingga akan lebih buruk dari itu.)
Ini adalah sumber YASM / NASM dengan %if
kondisional. Ia dapat dibangun dengan -felf32
(kode 32-bit) atau -felfx32
(kode 64-bit dengan ABI x32), dan dengan panjang implisit atau eksplisit . Saya sudah menguji semua 4 versi. Lihat jawaban ini untuk skrip untuk membuat biner statis dari sumber NASM / YASM.
Untuk menguji versi 64-bit pada mesin tanpa dukungan untuk ABI x32, Anda dapat mengubah regs penunjuk menjadi 64-bit. (Kemudian kurangi jumlah REX.W = 1 awalan (0x48 byte) dari hitungan. Dalam hal ini, 4 instruksi memerlukan awalan REX untuk beroperasi pada regs 64-bit). Atau cukup sebut saja dengan rsp
dan input pointer di ruang alamat 4G rendah.
%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.
Saya melihat bermain-main dengan DF (bendera arah yang mengontrol lodsd
/ scasd
dan sebagainya), tapi sepertinya tidak menang. ABI yang biasa mengharuskan DF dihapus pada entri fungsi dan keluar. Dengan asumsi dibersihkan pada entri tetapi membiarkannya ditetapkan pada saat keluar akan curang, IMO. Akan lebih baik menggunakan LODSD / SCASD untuk menghindari 3-byte sub esi, 4
, terutama dalam kasus di mana tidak ada sampah yang tinggi.
Strategi bitmap alternatif (untuk string panjang implisit x86-64)
Ternyata ini tidak menyimpan byte, karena bt r32,r32
masih berfungsi dengan sampah tinggi di bit-index. Hanya saja tidak terdokumentasi seperti apa shr
adanya.
Alih-alih bt / sbb
memasukkan bit ke / keluar dari CF, gunakan shift / mask untuk mengisolasi bit yang kita inginkan dari bitmap.
%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
Karena ini menghasilkan 0/1 dalam AL di akhir (bukan 0 / 0xFF), kita dapat melakukan inversi yang diperlukan dari nilai pengembalian di akhir fungsi dengan xor al, 1
(2B) alih-alih dec eax
(juga 2B di x86-64) untuk masih menghasilkan nilai yang layak bool
/_Bool
kembali.
Ini digunakan untuk menghemat 1B untuk x86-64 dengan string panjang implisit, dengan menghindari kebutuhan untuk nol byte tinggi EAX. (Saya telah menggunakan and eax, 0x7F ^ 0x20
untuk memaksa huruf besar dan nol sisa eax dengan 3-byte and r32,imm8
. Tapi sekarang saya menggunakan 2-byte langsung-dengan-AL pengkodean yang paling 8086 instruksi miliki, seperti yang sudah saya lakukan untuk sub
dan cmp
.)
Itu hilang ke bt
/ salc
dalam mode 32-bit, dan string panjang-eksplisit membutuhkan ECX untuk hitungan sehingga ini tidak berfungsi di sana juga.
Tetapi kemudian saya menyadari bahwa saya salah: bt edx, eax
masih bekerja dengan sampah tinggi di eax. Ini rupanya menutupi hitungan shift dengan cara yang sama shr r32, cl
tidak (melihat hanya pada 5 bit cl rendah). Ini berbeda dari bt [mem], reg
, yang dapat mengakses di luar memori yang dirujuk oleh mode / ukuran pengalamatan, memperlakukannya sebagai bitstring. (Gila CISC ...)
Manual referensi yang ditetapkan Intel tidak mendokumentasikan penutupan, jadi mungkin itu perilaku tidak berdokumen yang dipertahankan Intel untuk saat ini. (Hal semacam itu tidak jarang terjadi. bsf dst, src
Dengan src = 0 selalu meninggalkan dst tidak dimodifikasi, meskipun itu didokumentasikan untuk meninggalkan dst memegang nilai yang tidak ditentukan dalam kasus itu. AMD sebenarnya mendokumentasikan perilaku src = 0.) Saya menguji Skylake dan Core2, dan bt
versi ini bekerja dengan sampah non-nol di EAX di luar AL.
Trik yang rapi di sini adalah menggunakan xchg eax,ecx
(1 byte) untuk mendapatkan hitungan ke CL. Sayangnya, BMI2 shrx eax, edx, eax
adalah 5 byte, vs hanya 2 byte untuk shr eax, cl
. Menggunakan bextr
membutuhkan 2-byte mov ah,1
(untuk jumlah bit untuk diekstrak), jadi itu lagi 5 + 2 byte seperti SHRX + AND.
Kode sumber menjadi sangat berantakan setelah menambahkan %if
kondisional. Inilah pembongkaran string x32 implisit-panjang (menggunakan strategi alternatif untuk bitmap, jadi itu masih 46 byte).
Perbedaan utama dari versi panjang eksplisit di loop pertama. Perhatikan bagaimana ada lods
sebelumnya, dan di bagian bawah, bukan hanya satu di bagian atas loop.
; 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