x86-64机器代码,int64_t
输入12个字节
6字节double
输入
需要popcnt
ISA扩展名(CPUID.01H:ECX.POPCNT [Bit 23] = 1
)。
(或者13个字节,如果就地修改arg需要写入所有64位,而不是在前32位中留下垃圾。我认为有理由认为调用方可能只想加载低位的32b,而x86为零是合理的-每个32位操作都会将其从32隐式扩展到64。尽管如此,它确实会阻止调用者执行操作add rbx, [rdi]
或执行其他操作。)
x87指令比更明显的SSE2 cvtsi2sd
/ movq
(用于@ceilingcat的答案中)短,[reg]
寻址方式的大小与a相同reg
:只是一个mod / rm字节。
诀窍是想出一种将值传递到内存中的方法,而无需为寻址模式使用太多字节。(例如,在栈上传递不是那么好。)幸运的是,规则允许读/写args或单独的输出args,因此我可以让调用者向我传递指向允许写的内存的指针。
可从C调用并带有签名: void popc_double(int64_t *in_out);
仅结果的低32b有效,这对于C可能很奇怪,但对于asm很自然。(解决此问题需要在最终存储区(mov [rdi], rax
)上添加REX前缀,因此需要再增加一个字节。)在Windows上,请更改rdi
为rdx
,因为Windows不使用x86-64 System 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。
如果确实要争辩说我们可以使用指向输入整数的指针并销毁它,同时返回in的输出rax
,则只需省略mov [rdi], eax
from popcnt_double_outarg
,将其减少到10个字节。
替代方法,没有愚蠢的电话会议技巧,14字节
将堆栈用作暂存空间,并将其push
放到那里。使用push
/ pop
复制2个字节的寄存器,而不是3个字节mov rdi, rsp
。([rsp]
始终需要一个SIB字节,因此值得花两个字节来复制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
格式输入
问题只是说它是一个在一定范围内的整数,而不是必须以base2二进制整数表示。接受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)
。但是有一些x86机器代码的asm答案(例如codegolf.stackexchange.com/a/136360/30206和mine)都假设我们必须接受64位整数输入。接受一个binary64
值将节省5个字节。