รหัสเครื่อง i386 (x86-32), 8 ไบต์ (9B สำหรับการลงนาม)
+ 1B หากเราจำเป็นต้องจัดการb = 0
กับอินพุต
รหัสเครื่อง amd64 (x86-64), 9 ไบต์ (10B สำหรับผู้ลงนาม, หรือ14B 13B สำหรับจำนวนเต็ม 64b ที่ลงชื่อหรือไม่ได้ลงนาม)
10 9B สำหรับ amd64 ที่ไม่ได้ลงชื่อเมื่อมีการป้อนข้อมูลด้วย = 0
ปัจจัยการผลิตเป็น 32bit ไม่ใช่ศูนย์ลงนามจำนวนเต็มในและeax
เอาท์พุทecx
eax
## 32bit code, signed integers: eax, ecx
08048420 <gcd0>:
8048420: 99 cdq ; shorter than xor edx,edx
8048421: f7 f9 idiv ecx
8048423: 92 xchg edx,eax ; there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
8048424: 91 xchg ecx,eax ; eax = divisor(from ecx), ecx = remainder(from edx), edx = quotient(from eax) which we discard
; loop entry point if we need to handle ecx = 0
8048425: 41 inc ecx ; saves 1B vs. test/jnz in 32bit mode
8048426: e2 f8 loop 8048420 <gcd0>
08048428 <gcd0_end>:
; 8B total
; result in eax: gcd(a,0) = a
ecx = 0
โครงสร้างวงนี้ล้มเหลวในการทดสอบกรณีที่ ( div
ทำให้การ#DE
ประมวลผลของฮาร์ดแวร์หารด้วยศูนย์ (บน Linux เคอร์เนลจะส่งมอบSIGFPE
(ข้อยกเว้นจุดลอย)) หากจุดเข้าใช้วนรอบนั้นอยู่ตรงหน้าinc
เราจะหลีกเลี่ยงปัญหารุ่น x86-64 สามารถจัดการได้ ฟรีดูด้านล่าง
คำตอบไมค์ Shlanta เป็นจุดเริ่มต้นสำหรับการนี้ ห่วงฉันจะเป็นสิ่งเดียวกันเป็นของเขา แต่สำหรับจำนวนเต็มลงนามเพราะcdq
เป็นหนึ่งใน byter xor edx,edx
สั้นกว่า และใช่มันทำงานได้อย่างถูกต้องโดยมีอินพุตหนึ่งหรือทั้งสองเป็นลบ เวอร์ชันของ Mike จะทำงานเร็วขึ้นและใช้พื้นที่น้อยลงในแคช uop ( xchg
คือ 3 uops บน Intel CPUs และloop
ช้ามากใน CPUs ส่วนใหญ่ ) แต่รุ่นนี้จะชนะด้วยขนาดรหัสเครื่อง
ฉันไม่ได้สังเกตในตอนแรกว่าคำถามนั้นต้องการ32 บิตที่ไม่ได้ลงชื่อ กลับไปที่xor edx,edx
แทนที่จะcdq
เสียค่าใช้จ่ายหนึ่งไบต์ div
มีขนาดเท่าidiv
กันและทุกอย่างอื่นสามารถเหมือนเดิมได้ ( xchg
สำหรับการเคลื่อนย้ายข้อมูลและinc/loop
ยังใช้งานได้)
ที่น่าสนใจสำหรับตัวถูกดำเนินการขนาด 64 บิต ( rax
และrcx
) รุ่นที่เซ็นชื่อและไม่ได้ลงชื่อมีขนาดเท่ากัน เวอร์ชันที่ลงนามต้องการคำนำหน้า REX สำหรับcqo
(2B) แต่รุ่นที่ไม่ได้ลงชื่อยังคงสามารถใช้ 2B xor edx,edx
ได้
ในรหัส 64 บิตinc ecx
คือ 2B: ไบต์เดียวinc r32
และdec r32
opcodes ถูก repurposed เป็นคำนำหน้า REX inc/loop
จะไม่บันทึกรหัสขนาดในโหมด 64bit test/jnz
ดังนั้นคุณอาจได้เป็นอย่างดี การดำเนินงานในจำนวนเต็ม 64bit เพิ่มอีกหนึ่งไบต์ต่อการเรียนการสอนในคำนำหน้า REX ยกเว้นหรือloop
jnz
มันเป็นไปได้สำหรับส่วนที่เหลือที่จะมีศูนย์ทั้งหมดใน 32b ต่ำ (เช่นgcd((2^32), (2^32 + 1))
) ดังนั้นเราจึงจำเป็นที่จะทดสอบ RCX test ecx,ecx
ทั้งหมดและไม่สามารถบันทึกไบต์ด้วย อย่างไรก็ตามjrcxz
insn ที่ช้ากว่านั้นมีเพียง 2B และเราสามารถวางไว้ที่ด้านบนของลูปเพื่อจัดการecx=0
กับรายการ :
## 64bit code, unsigned 64 integers: rax, rcx
0000000000400630 <gcd_u64>:
400630: e3 0b jrcxz 40063d <gcd_u64_end> ; handles rcx=0 on input, and smaller than test rcx,rcx/jnz
400632: 31 d2 xor edx,edx ; same length as cqo
400634: 48 f7 f1 div rcx ; REX prefixes needed on three insns
400637: 48 92 xchg rdx,rax
400639: 48 91 xchg rcx,rax
40063b: eb f3 jmp 400630 <gcd_u64>
000000000040063d <gcd_u64_end>:
## 0xD = 13 bytes of code
## result in rax: gcd(a,0) = a
โปรแกรมทดสอบmain
ที่รันได้เต็มรูปแบบรวมถึงที่รันprintf("...", gcd(atoi(argv[1]), atoi(argv[2])) );
ซอร์สและเอาต์พุต asm บน Godbolt Compiler Explorerสำหรับเวอร์ชัน 32 และ 64b ทดสอบและการทำงานสำหรับ 32bit ( -m32
) 64bit ( -m64
) และx32 ABI (-mx32
)
รวมอยู่ด้วย: รุ่นที่ใช้การลบซ้ำ ๆ เท่านั้นซึ่งเป็น 9B สำหรับผู้ที่ไม่ได้ลงชื่อแม้สำหรับโหมด x86-64 และสามารถรับอินพุตหนึ่งตัวในการลงทะเบียนโดยพลการ อย่างไรก็ตามมันไม่สามารถจัดการอินพุตที่เป็น 0 ในรายการ (ตรวจพบเมื่อsub
สร้างศูนย์ซึ่ง x - 0 ไม่เคยทำ)
GNU C inline asm ซอร์สสำหรับรุ่น 32 บิต (คอมไพล์ด้วยgcc -m32 -masm=intel
)
int gcd(int a, int b) {
asm (// ".intel_syntax noprefix\n"
// "jmp .Lentry%=\n" // Uncomment to handle div-by-zero, by entering the loop in the middle. Better: `jecxz / jmp` loop structure like the 64b version
".p2align 4\n" // align to make size-counting easier
"gcd0: cdq\n\t" // sign extend eax into edx:eax. One byte shorter than xor edx,edx
" idiv ecx\n"
" xchg eax, edx\n" // there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
" xchg eax, ecx\n" // eax = divisor(ecx), ecx = remainder(edx), edx = garbage that we will clear later
".Lentry%=:\n"
" inc ecx\n" // saves 1B vs. test/jnz in 32bit mode, none in 64b mode
" loop gcd0\n"
"gcd0_end:\n"
: /* outputs */ "+a" (a), "+c"(b)
: /* inputs */ // given as read-write outputs
: /* clobbers */ "edx"
);
return a;
}
โดยปกติแล้วฉันจะเขียนฟังก์ชันทั้งหมดใน asm แต่ GNU C inline asm ดูเหมือนจะเป็นวิธีที่ดีที่สุดในการรวมตัวอย่างที่สามารถมี / ส่งออกในสิ่งที่เราเลือก อย่างที่คุณเห็น GNU C inline asm ไวยากรณ์ทำให้ asm น่าเกลียดและมีเสียงดัง นอกจากนี้ยังเป็นวิธีที่จริงยากที่จะเรียนรู้ asm
มันจะรวบรวมและทำงานใน.att_syntax noprefix
โหมดจริงเพราะ insns ทั้งหมดที่ใช้เป็นตัวถูกดำเนินการเดี่ยว / ไม่มีxchg
เลย ไม่ใช่การสังเกตที่มีประโยชน์จริงๆ