x86-32-Bit-Maschinencodefunktion, 42 bis 41 Byte
Derzeit die kürzeste Antwort in einer anderen Sprache als Golf, 1B kürzer als @ streetsters q / kdb + .
Mit 0 für wahr und ungleich Null für falsch: 41 40 Bytes. (Speichert im Allgemeinen 1 Byte für 32-Bit, 2 Byte für 64-Bit).
Mit Zeichenfolgen mit impliziter Länge (C-terminiert mit 0): 45 44 Bytes
x86-64-Maschinencode (mit 32-Bit-Zeigern wie dem x32-ABI): 44 43 Byte .
x86-64 mit Zeichenfolgen impliziter Länge, immer noch 46 Byte (die Shift / Mask-Bitmap-Strategie ist jetzt ausgeglichen).
Dies ist eine Funktion mit C-Signatur _Bool dennis_like(size_t ecx, const char *esi)
. Die Aufrufkonvention ist etwas ungewöhnlich und liegt in der Nähe von MS Vectorcall / Fastcall, weist jedoch unterschiedliche Arg-Register auf: String in ESI und Länge in ECX. Es blockiert nur seine Argumente und EDX. AL enthält den Rückgabewert, wobei die hohen Bytes Garbage enthalten (wie in den SysV x86- und x32-ABIs zulässig). IDK, was die ABIs von MS zu High-Garbage sagen, wenn Bool oder enge Ganzzahlen zurückgegeben werden.
Erklärung des Algorithmus :
Durchlaufen Sie die Eingabezeichenfolge, filtern und klassifizieren Sie sie in ein boolesches Array auf dem Stapel: Prüfen Sie für jedes Byte, ob es sich um ein alphabetisches Zeichen handelt (falls nicht, fahren Sie mit dem nächsten Zeichen fort), und wandeln Sie es in eine Ganzzahl von 0-25 (AZ) um. . Verwenden Sie diese 0-25-Ganzzahl, um eine Bitmap von Vokal = 0 / Konsonant = 1 zu überprüfen. (Die Bitmap wird als 32-Bit-Sofortkonstante in ein Register geladen.) Schieben Sie 0 oder 0xFF auf den Stapel entsprechend dem Bitmap-Ergebnis (tatsächlich im Low-Byte eines 32-Bit-Elements, das möglicherweise in den oberen 3 Bytes Müll enthält).
Die erste Schleife erzeugt ein Array von 0 oder 0xFF (in mit Garbage aufgefüllten Dword-Elementen). Führen Sie die übliche Palindromprüfung mit einer zweiten Schleife durch, die anhält, wenn sich die Zeiger in der Mitte kreuzen (oder wenn beide auf dasselbe Element zeigen, wenn eine ungerade Anzahl alphabetischer Zeichen vorhanden ist). Der sich nach oben bewegende Zeiger ist der Stapelzeiger, und wir verwenden POP zum Laden + Inkrementieren. Anstelle von compare / setcc in dieser Schleife können wir nur XOR verwenden, um dasselbe / unterschiedliches zu erkennen, da es nur zwei mögliche Werte gibt. Wir könnten (mit OR) akkumulieren, ob wir nicht passende Elemente gefunden haben, aber ein Early-Out-Zweig für Flags, die von XOR gesetzt wurden, ist mindestens genauso gut.
Beachten Sie, dass die zweite Schleife die byte
Operandengröße verwendet , sodass es egal ist, welchen Abfall die erste Schleife außerhalb des Low-Bytes jedes Array-Elements hinterlässt.
Es verwendet den undokumentierten salc
Befehl , um AL von CF auf die gleiche Weise zu setzen, wie dies der Fall sbb al,al
wäre. Es wird von jeder Intel-CPU unterstützt (außer im 64-Bit-Modus), sogar von Knight's Landing! Agner Fog listet das Timing auch für alle AMD-CPUs (einschließlich Ryzen) auf. Wenn also x86-Anbieter darauf bestehen, dieses Byte Opcode-Speicherplatz seit 8086 zu belegen, können wir es auch nutzen.
Interessante Tricks:
- Unsigned -Compare-Trick für eine Kombination aus isalpha () und toupper () und Null-Erweiterung des Bytes, um eax zu füllen, mit folgenden Einstellungen:
- sofortige Bitmap in einem Register für
bt
, inspiriert von einer netten Compiler-Ausgabe fürswitch
.
- Erstellen eines Arrays mit variabler Größe auf dem Stapel durch Drücken einer Schleife. (Standard für asm, aber nichts, was Sie mit C für die String-Version mit impliziter Länge tun können). Für jedes eingegebene Zeichen werden 4 Byte Stapelspeicherplatz benötigt, es wird jedoch mindestens 1 Byte im Vergleich zum optimalen Golfspiel gespart
stosb
.
- Anstelle von cmp / setne auf dem Booleschen Array werden XOR-Boolesche Werte zusammengefasst, um direkt einen Wahrheitswert zu erhalten. (
cmp
/ salc
ist keine Option, da salc
nur für CF funktioniert und 0xFF-0 CF nicht festlegt. Ist sete
3 Bytes, würde aber die inc
externe Schleife vermeiden , bei einem Nettokosten von 2 Bytes (1 im 64-Bit-Modus) )) vs. xor in the loop und fixieren mit 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
Dies ist wahrscheinlich auch eine der schnellsten Antworten, da keines der Golfspiele wirklich zu weh tut, zumindest für Zeichenfolgen mit weniger als ein paar tausend Zeichen, bei denen die 4x-Speichernutzung nicht viele Cache-Fehlschläge verursacht. (Es kann auch vorkommen, dass Antworten verloren gehen, die für nicht Dennis-ähnliche Zeichenfolgen vorzeitig beendet werden, bevor alle Zeichen durchlaufen werden .) Ist salc
langsamer als setcc
bei vielen CPUs (z. B. 3 Uops vs. 1 bei Skylake), aber eine Bitmap-Überprüfung mit bt/salc
ist immer noch schneller als eine String-Suche oder ein Regex-Match. Und es gibt keinen Start-Overhead, daher ist es für kurze Streicher extrem billig.
Dies in einem Durchgang zu tun, würde bedeuten, den Klassifizierungscode für die Aufwärts- und Abwärtsrichtung zu wiederholen. Das wäre schneller, aber größer. (Wenn Sie schnell sein wollen, können Sie mit SSE2 oder AVX2 natürlich 16 oder 32 Zeichen gleichzeitig machen, wobei Sie den Compare-Trick weiterhin anwenden, indem Sie den Bereich an den unteren Rand des vorzeichenbehafteten Bereichs verschieben.)
Testen Sie das Programm (für ia32 oder x32 Linux) , um diese Funktion mit einem cmdline-Argument aufzurufen, und beenden Sie es mit status = return value. strlen
Implementierung von 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
Eine 64-Bit-Version dieser Funktion könnte verwenden sbb eax,eax
, die nur 2 Bytes anstelle von 3 für ist setc al
. Es würde auch ein zusätzliches Byte für dec
oder not
am Ende benötigen (da nur 32-Bit 1-Byte-Inc / Dec-R32 hat). Mit dem x32-ABI (32-Bit-Zeiger im langen Modus) können wir REX-Präfixe immer noch vermeiden, obwohl wir Zeiger kopieren und vergleichen.
setc [rdi]
kann direkt in den Speicher schreiben, aber das Reservieren von ECX-Bytes Stapelspeicher kostet mehr Code als das spart. (Und wir müssen uns durch das Ausgabearray bewegen. Benötigt [rdi+rcx]
ein zusätzliches Byte für den Adressierungsmodus, aber wirklich brauchen wir einen Zähler, der für gefilterte Zeichen nicht aktualisiert wird, also wird es schlimmer sein.)
Dies ist die YASM / NASM-Quelle mit %if
Bedingungen. Es kann mit -felf32
(32-Bit-Code) oder -felfx32
(64-Bit-Code mit dem x32-ABI) und mit impliziter oder expliziter Länge erstellt werden . Ich habe alle 4 Versionen getestet. In dieser Antwort finden Sie ein Skript zum Erstellen einer statischen Binärdatei aus einer NASM / YASM-Quelle.
Um die 64-Bit-Version auf einem Computer ohne Unterstützung für das x32-ABI zu testen, können Sie die Zeigerregs in 64-Bit ändern. (Dann subtrahieren Sie einfach die Anzahl der REX.W = 1-Präfixe (0x48 Byte) von der Anzahl. In diesem Fall benötigen 4 Befehle REX-Präfixe, um mit 64-Bit-Registern zu arbeiten.) Oder rufen Sie es einfach mit dem rsp
und dem Eingabezeiger im niedrigen 4G-Adressraum auf.
%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.
Ich habe versucht, mit DF (der Richtungsfahne, die lodsd
/ scasd
und so weiter steuert ) herumzuspielen , aber es schien einfach kein Gewinn zu sein. Die üblichen ABIs erfordern, dass DF beim Funktionseintritt und -austritt gelöscht wird. Angenommen, beim Betreten gelöscht, aber beim Verlassen gesetzt, wäre Betrug, IMO. Es wäre schön, LODSD / SCASD zu verwenden, um die 3-Byte-Größe zu vermeiden sub esi, 4
, insbesondere in Fällen, in denen kein hoher Müll vorhanden ist.
Alternative Bitmap-Strategie (für Zeichenfolgen mit impliziter Länge von x86-64)
Es stellt sich heraus, dass dies keine Bytes spart, da es bt r32,r32
immer noch mit hohem Müll im Bit-Index funktioniert. Es ist einfach nicht dokumentiert, wie es shr
ist.
bt / sbb
Verwenden Sie eine Verschiebung / Maske, um das gewünschte Bit von der Bitmap zu isolieren, anstatt das Bit in die / aus der CF zu holen.
%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
Da dies in AL am Ende 0/1 ergibt (statt 0 / 0xFF), können wir die notwendige Invertierung des Rückgabewerts am Ende der Funktion mit xor al, 1
(2B) statt dec eax
(auch 2B in x86-64) durchführen erzeugen immer noch einen richtigen bool
/_Bool
Rückgabewert.
Dadurch wurde 1B für x86-64 mit Zeichenfolgen impliziter Länge eingespart, da die hohen Bytes von EAX nicht auf Null gesetzt werden müssen. (Früher habe ich and eax, 0x7F ^ 0x20
den Rest von eax mit einem 3-Byte-Code in Großbuchstaben geschrieben und auf Null gesetzt and r32,imm8
. Jetzt verwende ich jedoch die 2-Byte-Sofort-mit-AL-Codierung, die die meisten 8086-Befehle haben, wie ich es bereits getan habe für die sub
und cmp
.)
Es verliert an bt
/ salc
in 32-Bit - Modus, und explizite Zeichenfolge der Länge benötigen ECX für die Zählung so ist dies dort entweder nicht funktionieren.
Aber dann habe ich gemerkt, dass ich mich geirrt habe: arbeitet bt edx, eax
immer noch mit Hochmüll in eax. Es scheint Masken der Verschiebung zählen die gleiche Art und Weise shr r32, cl
nicht (nur auf der Suche bei dem niedrigen 5 Bits cl). Dies unterscheidet sich davon bt [mem], reg
, dass der Zugriff außerhalb des Speichers, auf den durch den Adressierungsmodus / die Adressierungsgröße verwiesen wird, als Bitstring behandelt werden kann. (Crazy CISC ...)
Intels Insn-Set-Ref-Handbuch dokumentiert die Maskierung nicht. Daher ist es möglicherweise undokumentiertes Verhalten, das Intel derzeit beibehält. (So etwas ist nicht ungewöhnlich. bsf dst, src
Mit src = 0 bleibt dst immer unverändert, auch wenn dokumentiert ist, dass dst in diesem Fall einen undefinierten Wert enthält. AMD dokumentiert tatsächlich das Verhalten von src = 0.) Ich habe es mit Skylake und Core2 getestet. und die bt
Version arbeitet mit nicht-Null-Müll in EAX außerhalb von AL.
Ein guter Trick ist hier xchg eax,ecx
(1 Byte), um die Zählung in CL zu bekommen. Leider ist BMI2 shrx eax, edx, eax
5 Bytes , verglichen mit nur 2 Bytes für shr eax, cl
. Für die Verwendung wird bextr
ein 2-Byte-Wert mov ah,1
(für die Anzahl der zu extrahierenden Bits) benötigt, daher sind es wieder 5 + 2 Byte wie bei SHRX + AND.
Der Quellcode ist nach dem Hinzufügen von %if
Bedingungen ziemlich chaotisch geworden . Hier ist das Zerlegen von x32-Zeichenfolgen mit impliziter Länge (unter Verwendung der alternativen Strategie für die Bitmap, also immer noch 46 Byte).
Der Hauptunterschied zur expliziten Länge liegt in der ersten Schleife. Beachten Sie, wie es ein lods
davor und am unteren Rand gibt, anstatt nur eines am oberen Rand der Schleife.
; 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