x86-64-Maschinencode, 12 Byte für int64_t
Eingabe
6 Bytes für double
Eingabe
Benötigt die popcnt
ISA-Erweiterung (CPUID.01H:ECX.POPCNT [Bit 23] = 1
).
(Oder 13 Bytes, wenn das Ändern des Arg an Ort und Stelle das Schreiben aller 64-Bits erfordert, anstatt Müll in den oberen 32 zu belassen. Ich denke, es ist vernünftig zu argumentieren, dass der Aufrufer wahrscheinlich nur die niedrigen 32b und x86 Null laden möchte -Erweitert sich implizit mit jeder 32-Bit-Operation von 32 auf 64. Trotzdem wird der Aufrufer davon abgehalten, add rbx, [rdi]
etwas zu tun oder so.)
x87-Anweisungen sind kürzer als die offensichtliche SSE2 cvtsi2sd
/ movq
(in der Antwort von @ ceilingcat verwendet ), und ein [reg]
Adressierungsmodus ist so groß wie einereg
: nur ein mod / rm-Byte.
Der Trick bestand darin, einen Weg zu finden, wie der Wert im Speicher übergeben werden kann, ohne dass zu viele Bytes für die Adressierungsmodi benötigt werden. (z. B. ist das Weitergeben des Stapels nicht so toll.) Glücklicherweise erlauben die Regeln Lese- / Schreib-Args oder separate Ausgabeargs , sodass ich den Aufrufer einfach dazu bringen kann, mir einen Zeiger auf den Speicher zu übergeben, den ich schreiben darf.
Aufrufbar von C mit der Signatur: void popc_double(int64_t *in_out);
Nur die niedrigen 32b des Ergebnisses sind gültig, was für C vielleicht seltsam, für asm aber natürlich ist. (Um dies zu beheben, ist ein REX-Präfix im endgültigen Speicher ( mov [rdi], rax
) erforderlich , also ein weiteres Byte.) Ändern Sie unter Windows rdi
zu rdx
, da Windows das x86-64-System-V-ABI nicht verwendet.
NASM-Auflistung. Der TIO-Link enthält den Quellcode ohne Demontage.
1 addr machine global popcnt_double_outarg
2 code popcnt_double_outarg:
3 ;; normal x86-64 ABI, or x32: void pcd(int64_t *in_out)
4 00000000 DF2F fild qword [rdi] ; int64_t -> st0
5 00000002 DD1F fstp qword [rdi] ; store binary64, using retval as scratch space.
6 00000004 F3480FB807 popcnt rax, [rdi]
7 00000009 8907 mov [rdi], eax ; update only the low 32b of the in/out arg
8 0000000B C3 ret
# ends at 0x0C = 12 bytes
Probieren Sie es online! Beinhaltet a_start
Testprogramm, das ihm einen Wert übergibt und mit dem Rückgabewert exit status = popcnt beendet wird. (Öffnen Sie die Registerkarte "Debug", um sie anzuzeigen.)
Das Übergeben separater Eingabe- / Ausgabezeiger würde ebenfalls funktionieren (rdi und rsi in der x86-64-SystemV-ABI), aber dann können wir die 64-Bit-Eingabe nicht vernünftigerweise zerstören oder die Notwendigkeit eines 64-Bit-Ausgabepuffers genauso einfach rechtfertigen, während nur die geschrieben wird niedrig 32b.
Wenn wir argumentieren möchten, dass wir einen Zeiger auf die ganze Zahl der Eingabe nehmen und sie zerstören können, während wir die Ausgabe zurückgeben rax
, lassen Sie einfach das mov [rdi], eax
from weg popcnt_double_outarg
und bringen Sie es auf 10 Bytes herunter.
Alternative ohne alberne Calling-Convention-Tricks, 14 Bytes
Verwenden Sie den Stapel als Arbeitsfläche, push
um ihn dorthin zu bringen. Verwenden Sie push
/, pop
um Register in 2 Bytes anstelle von 3 für zu kopieren mov rdi, rsp
. ( [rsp]
Benötigt immer ein SIB-Byte, es lohnt sich also, 2 Byte für das Kopieren auszugeben, rsp
bevor drei Anweisungen es verwenden.)
Anruf von C mit dieser Signatur: int popcnt_double_push(int64_t);
11 global popcnt_double_push
12 popcnt_double_push:
13 00000040 57 push rdi ; put the input arg on the stack (still in binary integer format)
14 00000041 54 push rsp ; pushes the old value (rsp updates after the store).
15 00000042 5A pop rdx ; mov rdx, rsp
16 00000043 DF2A fild qword [rdx]
17 00000045 DD1A fstp qword [rdx]
18 00000047 F3480FB802 popcnt rax, [rdx]
19 0000004C 5F pop rdi ; rebalance the stack
20 0000004D C3 ret
next byte is 0x4E, so size = 14 bytes.
Eingabe im double
Format übernehmen
Die Frage besagt nur, dass es sich um eine Ganzzahl in einem bestimmten Bereich handelt und nicht, dass es sich um eine binäre Ganzzahldarstellung zur Basis 2 handeln muss. Das Akzeptieren von double
Eingaben macht die Verwendung von x87 überflüssig. (Es sei denn, Sie verwenden eine benutzerdefinierte Aufrufkonvention, bei der double
s in x87-Registern übergeben wird. Anschließend in der roten Zone unter dem Stapel speichern und von dort aus einfügen.)
11 Bytes:
57 00000110 66480F7EC0 movq rax, xmm0
58 00000115 F3480FB8C0 popcnt rax, rax
59 0000011A C3 ret
Wir können jedoch den gleichen Trick wie zuvor verwenden, um eine 6-Byte-Version zu erstellen: int pcd(const double&d);
58 00000110 F3480FB807 popcnt rax, [rdi]
59 00000115 C3 ret
6 Bytes .
binary64
wenn sie wollen? Einige Leute (einschließlich ich selbst) interpretierten die Frage so, dass Funktionen Eingaben als Integer-Typ wie C akzeptieren müssenlong
. In C können Sie argumentieren, dass die Sprache für Sie konvertiert wird, genau wie wenn Sie anrufensqrt((int)foo)
. Es gibt jedoch einige x86-Maschinencode-asm-Antworten (wie codegolf.stackexchange.com/a/136360/30206 und meine), die beide davon ausgehen, dass wir 64-Bit-Integer-Eingaben akzeptieren müssen. Das Akzeptieren einesbinary64
Wertes würde 5 Bytes sparen.