машинний код x86-64, 12 байт для int64_t
введення
6 байт для double
введення
Потрібно popcnt
розширення ISA ( CPUID.01H:ECX.POPCNT [Bit 23] = 1
).
(Або 13 байт, якщо для зміни аргументу на місці потрібно записати всі 64-бітні замість того, щоб залишати сміття у верхній частині 32. Я думаю, що розумно стверджувати, що абонент, ймовірно, хотів би завантажити хоч низький 32b, і нуль x86 -розширюється від 32 до 64 неявно під час кожної 32-бітної операції, але це не зупиняє абонента робити add rbx, [rdi]
чи щось подібне.)
Інструкції x87 коротші, ніж більш очевидна SSE2 cvtsi2sd
/ movq
(використовується у відповіді на @ roofcat ), а [reg]
режим адресації має той самий розмір, що і reg
: лише байт mod / rm.
Трюк полягав у тому, щоб придумати спосіб передавати значення в пам'яті, не потребуючи занадто багато байтів для адресних режимів. (наприклад, передача стека не така вже й велика.) На щастя, правила дозволяють аргументи для читання / запису або окремі вихідні аргументи , тому я можу просто заставити абонента передати мені вказівник на пам'ять, яку мені дозволяється писати.
Зателефонувавши з C з підписом: void popc_double(int64_t *in_out);
Дійсно лише низький 32b результату, що може бути дивним для C, але природним для asm. (Для виправлення цього потрібен префікс REX на остаточному сховищі ( mov [rdi], rax
), тому ще один байт.) У 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. (Відкрийте вкладку "налагодження", щоб побачити її.)
Передача окремих покажчиків вводу / виводу також спрацювала (rdi та rsi в x86-64 SystemV ABI), але тоді ми не можемо розумно знищити 64-бітний вхід або як легко обгрунтувати необхідність 64-бітового вихідного буфера під час запису лише низький 32b.
Якщо ми хочемо стверджувати, що ми можемо взяти покажчик на вхідне ціле число і знищити його, повертаючи при цьому вихід rax
, тоді просто опустімо mov [rdi], eax
з popcnt_double_outarg
, знизивши його до 10 байт.
Альтернатива без дурних викликів-конвенцій, 14 байт
використовувати стек як місце для нуля, push
щоб дістати його туди. Використовуйте push
/ pop
для копіювання регістрів у 2 байти замість 3 для 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
форматі
Питання просто говорить про те, що це ціле число в певному діапазоні, а не те, що воно повинно бути в двійковому цілому поданні 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 (як codegolf.stackexchange.com/a/136360/30206 і моя), які припускали, що нам доведеться приймати 64-бітні цілі числа. Прийняттяbinary64
значення дозволить заощадити 5 байт.