x86-64 머신 코드, int64_t
입력 용 12 바이트
double
입력 용 6 바이트
popcnt
ISA 확장 ( CPUID.01H:ECX.POPCNT [Bit 23] = 1
)이 필요합니다 .
(또는 그 자리에서 arg를 수정하면 13 바이트는 상위 32에 쓰레기를 남기지 않고 모든 64 비트를 작성해야합니다. 어쨌든 호출자가 아마도 낮은 32b 만로드하고 싶을 것이라고 주장하는 것이 합리적이라고 생각합니다. -32 비트 작업마다 암시 적으로 32에서 64로 확장되지만 여전히 호출자가 수행하지 못하도록합니다 add rbx, [rdi]
.)
x87 명령어는보다 명백한 SSE2 cvtsi2sd
/ 보다 짧으며 movq
( @ceilingcat의 답변에 사용 ) [reg]
어드레싱 모드는 reg
mod / rm 바이트 와 같은 크기 입니다.
트릭은 주소 지정 모드에 너무 많은 바이트가 필요하지 않고 메모리에 값을 전달하는 방법을 고안하는 것이 었습니다. (예를 들어 스택에 전달하는 것은 그리 좋지 않습니다.) 다행히도, 규칙은 읽기 / 쓰기 args 또는 별도의 출력 args 를 허용하므로 호출자가 내가 쓸 수있는 메모리에 대한 포인터를 전달하도록 할 수 있습니다.
서명이있는 C에서 호출 가능 : void popc_double(int64_t *in_out);
결과의 낮은 32b 만 유효합니다. C에는 이상하지만 asm에는 자연 스럽습니다. (이 문제를 해결하는 것은 최종 저장 (에 REX 접두사가 필요 mov [rdi], rax
윈도우, 변화에), 그래서 하나 더 바이트.) rdi
에를 rdx
Windows가 x86-64의 시스템 V ABI를 사용하지 않기 때문에.
NASM 리스팅. TIO 링크에는 디스 어셈블리없이 소스 코드가 있습니다.
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
온라인으로 사용해보십시오!_start
값을 전달하고 종료 상태 = popcnt 리턴 값으로 종료 하는 테스트 프로그램을 포함 합니다. ( "디버그"탭을 열어서보십시오.)
별도의 입력 / 출력 포인터를 전달해도 작동하지만 (x86-64 SystemV ABI의 rdi 및 rsi) 64 비트 입력을 합리적으로 파괴하거나 64 비트 출력 버퍼가 필요한 것을 쉽게 정당화 할 수는 없습니다. 낮은 32b.
입력 정수에 대한 포인터를 가져 와서 출력을 반환하는 동안 파괴 할 수 있다고 주장 rax
하려면 mov [rdi], eax
from을 생략하십시오 .popcnt_double_outarg
가져 와서 10 바이트로 내립니다.
어리석은 전화-컨벤션 트릭이없는 대안, 14 바이트
스택을 스크래치 공간으로 사용하십시오 push
. 에 대해 3 대신에 2 바이트로 레지스터를 복사 하려면 push
/ pop
를 사용하십시오 mov rdi, rsp
. ( [rsp]
항상 SIB 바이트가 필요하므로 복사하는 데 2 바이트를 소비 할 가치가 있습니다.rsp
사용하는 세 개의 명령어 전에 하기 하는 것이 좋습니다.)
이 서명으로 C에서 전화하십시오. 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.
double
형식의 입력 허용
질문은 단지 그것이 범위 2의 정수 표현이 아니라 특정 범위의 정수라고 말합니다. double
입력을 수락 하면 x87을 더 이상 사용할 필요가 없습니다. double
s가 x87 레지스터로 전달 되는 사용자 지정 호출 규칙을 사용하지 않는 한 스택 아래의 빨간색 영역에 저장하고 거기에서 popcnt합니다.
11 바이트 :
57 00000110 66480F7EC0 movq rax, xmm0
58 00000115 F3480FB8C0 popcnt rax, rax
59 0000011A C3 ret
그러나 우리는 6 바이트 버전을 만들기 위해 이전과 동일한 참조 기준 트릭을 사용할 수 있습니다 int pcd(const double&d);
58 00000110 F3480FB807 popcnt rax, [rdi]
59 00000115 C3 ret
6 바이트 .
binary64
원하는 경우 함수가 이미 부동 소수점 형식으로 입력을 받아 들일 수 있도록 하시겠습니까? 일부 사람들 (처음에는 자신을 포함하여)은 함수가 입력을 C와 같은 정수 유형으로 허용해야한다고 질문을 해석했습니다long
. C에서는 호출 할 때와 같이 언어가 자동으로 변환 될 것이라고 주장 할 수 있습니다sqrt((int)foo)
. 그러나 64 비트 정수 입력을 수락해야한다고 가정 한 일부 x86 기계 코드 asm 답변 ( codegolf.stackexchange.com/a/136360/30206 및 광산)이 있습니다.binary64
값을 수락 하면 5 바이트가 절약됩니다.