x86-64マシンコード、int64_t
入力用12バイト
double
入力用に6バイト
popcnt
ISA拡張(CPUID.01H:ECX.POPCNT [Bit 23] = 1
)が必要です。
(または、引数をインプレースで変更する場合、上位32ビットにガベージを残すのではなく、すべての64ビットを書き込む必要がある場合は13バイトです。 -すべての32ビット操作で暗黙的に32から64に拡張します。それでも、呼び出し元の実行add rbx, [rdi]
などを停止します。
x87命令は、より明白なSSE2 cvtsi2sd
/ movq
(@ceilingcatの回答で使用)よりも短く、[reg]
アドレス指定モードはaと同じサイズreg
です:ちょうどmod / rmバイト。
トリックは、アドレス指定モードに多すぎるバイトを必要とせずに、値をメモリに渡す方法を考え出すことでした。(たとえば、スタックを渡すことはそれほど素晴らしいことではありません。)幸いなことに、ルールでは読み取り/書き込み引数、または個別の出力引数が許可されているので、呼び出し元に、書き込みが許可されているメモリへのポインターを渡してもらうことができます。
署名付きでCから呼び出し可能: void popc_double(int64_t *in_out);
結果の下位32bのみが有効です。これは、Cでは奇妙かもしれませんが、asmでは自然です。(これを修正するには、最終ストア(mov [rdi], rax
)にREXプレフィックスが必要なので、もう1バイト。)Windowsではx86-64 System V ABIを使用しないため、に変更rdi
しrdx
ます。
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
値を渡し、exit status = popcnt戻り値で終了 するテストプログラムが含まれています。(「デバッグ」タブを開いて確認してください。)
個別の入力/出力ポインタを渡すことも機能しますが(x86-64 SystemV ABIのrdiとrsi)、64ビット入力を合理的に破棄することはできません。低い32b。
入力整数へのポインタを取得して破棄し、出力をin rax
に戻すことができると主張したい場合は、単にmov [rdi], eax
fromを省略popcnt_double_outarg
して10バイトにします。
愚かな呼び出し規約トリックのない代替、14バイト
スタックをスクラッチスペースとして使用して、push
そこに到達します。push
/ pop
を使用して、レジスタを3バイトではなく2バイトでコピーしmov rdi, rsp
ます。([rsp]
常にSIBバイトが必要なので、それrsp
を使用する3つの命令の前にコピーするのに2バイトを費やす価値があります。)
この署名を使用して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や私のようなもの)がいくつかあり、どちらも64ビット整数入力を受け入れなければならないと仮定していました。binary64
値を受け入れると、5バイト節約されます。