รหัสเครื่อง x86-64, 12 ไบต์สำหรับint64_t
อินพุต
6 ไบต์สำหรับdouble
อินพุต
ต้องการpopcnt
ส่วนขยาย ISA ( CPUID.01H:ECX.POPCNT [Bit 23] = 1
)
(หรือ 13 ไบต์หากการแก้ไข ARG แบบแทนที่ต้องเขียน 64- บิตทั้งหมดแทนที่จะทิ้งขยะใน 32 ด้านบนฉันคิดว่ามันสมเหตุสมผลที่จะโต้แย้งว่าผู้เรียกอาจต้องการโหลด 32b ต่ำเท่านั้นและ x86 ศูนย์ - ขยายจาก 32 ถึง 64 โดยนัยกับการดำเนินการทุก 32- บิต แต่มันจะหยุดไม่ให้ผู้เรียกทำadd rbx, [rdi]
หรืออะไรบางอย่าง)
คำแนะนำ x87 จะสั้นกว่า SSE2 cvtsi2sd
/ ชัดเจนมากขึ้นmovq
(ใช้ในคำตอบของ @ ceilingcat ) และ[reg]
โหมดการกำหนดแอดเดรสนั้นมีขนาดเท่ากับ a reg
: เพียงแค่ mod / rm byte
เคล็ดลับคือการหาวิธีส่งค่าในหน่วยความจำโดยไม่จำเป็นต้องมีไบต์มากเกินไปสำหรับการกำหนดโหมด (เช่นการส่งผ่านสแต็กนั้นไม่ได้ยอดเยี่ยม) โชคดีที่กฎอนุญาตให้อ่าน / เขียน args หรือแยกเอาท์พุท argsดังนั้นฉันสามารถให้ผู้โทรส่งค่าตัวชี้ไปยังหน่วยความจำที่ฉันอนุญาตให้เขียนได้
เรียกได้จาก 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- บิตได้อย่างสมเหตุสมผลพอสมควร ต่ำ 32b
หากเราต้องการโต้แย้งว่าเราสามารถนำตัวชี้ไปยังจำนวนเต็มอินพุทและทำลายมันได้ในขณะที่ส่งคืนเอาต์พุตกลับมาrax
ก็เพียงแค่ละเว้นmov [rdi], eax
จากpopcnt_double_outarg
นั้นนำมาไว้ที่ 10 ไบต์
ทางเลือกโดยไม่มีเทคนิคการเรียกประชุมแบบโง่ 14 ไบต์
ใช้สแต็กเป็นพื้นที่เริ่มต้นpush
เพื่อไปที่นั่น การใช้push
/ pop
การคัดลอกลงทะเบียนใน 2 ไบต์แทน mov rdi, rsp
3 ( [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
แต่เราสามารถใช้เคล็ดลับ Pass-by-Reference เช่นเดียวกับก่อนหน้านี้เพื่อสร้างเวอร์ชัน 6 ไบต์: int pcd(const double&d);
58 00000110 F3480FB807 popcnt rax, [rdi]
59 00000115 C3 ret
6 ไบต์
binary64
รูปแบบทศนิยมหรือไม่หากต้องการ? บางคน (รวมทั้งตัวเองในขั้นต้น) ได้รับการตีความคำถามเป็นฟังก์ชั่นที่จำเป็นต้องยอมรับปัจจัยการผลิตเป็นชนิดจำนวนเต็มเช่นlong
C ใน Csqrt((int)foo)
คุณสามารถยืนยันว่าภาษาจะแปลงสำหรับคุณเช่นเดียวกับเมื่อคุณเรียก แต่มีรหัสเครื่อง x86 บางคำตอบ asm (เช่นcodegolf.stackexchange.com/a/136360/30206และของฉัน) ซึ่งทั้งคู่ตั้งสมมติฐานว่าเราต้องยอมรับอินพุตจำนวนเต็ม 64 บิต การยอมรับbinary64
ค่าจะประหยัดได้ 5 ไบต์