ฉันสังเกตเห็นว่าไม่มีคำถามดังกล่าวดังนั้นที่นี่:
คุณมีเคล็ดลับทั่วไปสำหรับการเล่นกอล์ฟในรหัสเครื่องหรือไม่? หากเคล็ดลับนี้ใช้กับสภาพแวดล้อมบางอย่างหรือการประชุมที่โทรมาเท่านั้นโปรดระบุในคำตอบของคุณ
กรุณาเพียงหนึ่งเคล็ดลับต่อคำตอบ (ดูที่นี่ )
ฉันสังเกตเห็นว่าไม่มีคำถามดังกล่าวดังนั้นที่นี่:
คุณมีเคล็ดลับทั่วไปสำหรับการเล่นกอล์ฟในรหัสเครื่องหรือไม่? หากเคล็ดลับนี้ใช้กับสภาพแวดล้อมบางอย่างหรือการประชุมที่โทรมาเท่านั้นโปรดระบุในคำตอบของคุณ
กรุณาเพียงหนึ่งเคล็ดลับต่อคำตอบ (ดูที่นี่ )
คำตอบ:
mov
- ระดับกลางมีราคาแพงสำหรับค่าคงที่นี่อาจชัดเจน แต่ฉันจะยังคงวางไว้ที่นี่ โดยทั่วไปแล้วจะคิดออกเกี่ยวกับการเป็นตัวแทนระดับบิตของจำนวนเมื่อคุณต้องการเริ่มต้นค่า
eax
ด้วย0
:b8 00 00 00 00 mov $0x0,%eax
ควรย่อให้สั้นลง ( เพื่อประสิทธิภาพเช่นเดียวกับขนาดรหัส )
31 c0 xor %eax,%eax
eax
ด้วย-1
:b8 ff ff ff ff mov $-1,%eax
สามารถตัดให้สั้นลง
31 c0 xor %eax,%eax
48 dec %eax
หรือ
83 c8 ff or $-1,%eax
หรือมากกว่าโดยทั่วไปค่าที่ขยายเพิ่ม 8 บิตสามารถสร้างได้ใน 3 ไบต์ด้วยpush -12
(2 ไบต์) / pop %eax
(1 ไบต์) สิ่งนี้ใช้ได้กับการลงทะเบียน 64 บิตโดยไม่มีส่วนนำหน้า REX เพิ่มเติม push
/ pop
default operand-size = 64
6a f3 pushq $0xfffffffffffffff3
5d pop %rbp
หรือได้รับค่าคงที่เป็นที่รู้จักในการลงทะเบียนคุณสามารถสร้างค่าคงที่ใกล้เคียงอื่นโดยใช้lea 123(%eax), %ecx
(3 ไบต์) สิ่งนี้มีประโยชน์ถ้าคุณต้องการการลงทะเบียนแบบ zeroed และค่าคงที่; xor-zero (2 bytes) + lea-disp8
(3 bytes)
31 c0 xor %eax,%eax
8d 48 0c lea 0xc(%eax),%ecx
ดูเพิ่มเติมตั้งค่าบิตทั้งหมดใน CPU register เป็น 1 อย่างมีประสิทธิภาพ
dec
เช่นxor eax, eax; dec eax
push imm8
/ pop reg
คือ 3 ไบต์และยอดเยี่ยมสำหรับค่าคงที่ 64 บิตใน x86-64 โดยที่dec
/ inc
เป็น 2 ไบต์ และpush r64
/ pop 64
(2 ไบต์) สามารถแทนที่ 3 ไบต์mov r64, r64
(3 ไบต์ด้วย REX) ดูเพิ่มเติมตั้งค่าบิตทั้งหมดใน CPU register เป็น 1 ได้อย่างมีประสิทธิภาพสำหรับสิ่งที่ต้องการlea eax, [rcx-1]
ค่าที่รู้จักในeax
(เช่นถ้าต้องการ registered zeroed และค่าคงที่อื่นเพียงแค่ใช้ LEA แทน push / pop
ในหลายกรณีคำแนะนำที่อิงกับแอคคูเลเตอร์ (เช่นคำสั่งที่ใช้(R|E)AX
เป็นตัวถูกดำเนินการปลายทาง) จะมีขนาด 1 ไบต์สั้นกว่าคำแนะนำกรณีทั่วไป ดูคำถามนี้ใน StackOverflow
al, imm8
กรณีพิเศษเช่นor al, 0x20
/ sub al, 'a'
/ cmp al, 'z'-'a'
/ ja .non_alphabetic
เป็น 2 ไบต์แต่ละแทน 3. การใช้al
ข้อมูลตัวละครยังช่วยให้และlodsb
/ หรือ stosb
หรือใช้al
เพื่อทดสอบบางอย่างเกี่ยวกับไบต์ต่ำของ EAX เช่นlodsd
/ test al, 1
/ setnz cl
ทำให้ cl = 1 หรือ 0 เป็นเลขคี่ / คู่ แต่ในกรณีที่หายากที่คุณจำเป็นต้องมี 32 บิตทันทีแล้วแน่ใจop eax, imm32
เหมือนในคำตอบของความเข้มของสีที่สำคัญของฉัน
ภาษาของคำตอบของคุณคือ asm (จริง ๆ แล้วรหัสเครื่อง) ดังนั้นให้ถือว่าเป็นส่วนหนึ่งของโปรแกรมที่เขียนด้วย asm ไม่ใช่ C-compiled-for-x86 ฟังก์ชั่นของคุณไม่จำเป็นต้องโทรออกได้ง่ายจาก C กับแบบแผนการโทรมาตรฐานใด ๆ นั่นเป็นโบนัสที่ดีถ้ามันไม่ทำให้คุณเสียค่าใช้จ่ายเพิ่ม
ในโปรแกรม asm ล้วนเป็นเรื่องปกติสำหรับฟังก์ชั่นตัวช่วยบางอย่างที่จะใช้แบบแผนการโทรที่สะดวกสำหรับพวกเขาและสำหรับผู้โทร ฟังก์ชั่นดังกล่าวจัดทำเอกสารแผนการประชุมที่เรียกว่า
ในชีวิตจริงแม้โปรแกรม asm จะทำ (ฉันคิดว่า) มีแนวโน้มที่จะใช้แบบแผนการเรียกที่สอดคล้องกันสำหรับฟังก์ชั่นส่วนใหญ่ ใน code-golf คุณกำลังปรับอึออกจากฟังก์ชั่นเดียวดังนั้นจึงเป็นสิ่งสำคัญ / พิเศษ
เพื่อทดสอบการทำงานของคุณจากโปรแกรม C, สามารถเขียนเสื้อคลุมที่ args ทำให้ในสถานที่ที่เหมาะสมจะช่วยประหยัด / คืนค่าลงทะเบียนพิเศษใด ๆ ที่คุณข่มขี่และทำให้ค่าส่งกลับเข้าไปe/rax
ถ้ามันไม่ได้มีอยู่แล้ว
การกำหนดให้ DF (แฟล็กทิศทางสตริงสำหรับlods
/ stos
/ ฯลฯ ) ให้ชัดเจน (ขึ้นไป) เมื่อมีการโทร / ret เป็นเรื่องปกติ ปล่อยให้มันไม่ได้กำหนดไว้ในการโทร / ret จะเป็น ok ต้องการให้ล้างหรือตั้งค่าในรายการ แต่ปล่อยให้มันแก้ไขเมื่อคุณกลับมาจะแปลก
การส่งคืนค่า FP ใน x87 st0
นั้นสมเหตุสมผล แต่การกลับมาst3
พร้อมกับขยะในการลงทะเบียน x87 อื่น ๆ นั้นไม่ใช่ ผู้เรียกจะต้องล้างค่าสแต็ก x87 แม้จะกลับมาst0
พร้อมกับรีจิสเตอร์สแต็กที่ไม่ว่างเปล่าที่สูงขึ้นก็จะเป็นที่น่าสงสัย (เว้นแต่คุณจะส่งคืนค่าหลายค่า)
call
ดังนั้นจึง[rsp]
เป็นที่อยู่ผู้ส่งของคุณ คุณสามารถหลีกเลี่ยงcall
/ ret
บน x86 โดยใช้ลิงค์ลงทะเบียนเช่นlea rbx, [ret_addr]
/ jmp function
และกลับด้วยjmp rbx
แต่นั่นไม่ใช่ "สมเหตุสมผล" นั่นไม่ได้มีประสิทธิภาพเท่ากับการโทร / รับดังนั้นจึงไม่ใช่สิ่งที่คุณจะพบได้ในรหัสจริงกรณีชายแดน: การเขียนฟังก์ชั่นที่ผลิตลำดับในอาร์เรย์ที่กำหนด 2 องค์ประกอบแรกเป็น args ฉันเลือกให้ผู้โทรจัดเก็บการเริ่มต้นของลำดับลงในอาร์เรย์และเพียงแค่ส่งตัวชี้ไปยังอาร์เรย์ นี่คือการดัดข้อกำหนดของคำถาม ผมถือว่าการ args บรรจุเข้าxmm0
สำหรับmovlps [rdi], xmm0
ซึ่งยังจะเป็นเรียกประชุมแปลก
การเรียกใช้ระบบ OS X ทำได้ ( CF=0
หมายถึงไม่มีข้อผิดพลาด): มันถือว่าเป็นการปฏิบัติที่ไม่ถูกต้องหรือไม่ที่จะใช้การลงทะเบียนค่าสถานะเป็นค่าส่งคืนบูลีน? .
เงื่อนไขใด ๆ ที่สามารถตรวจสอบได้ด้วย JCC หนึ่งรายการนั้นสมเหตุสมผลอย่างสมบูรณ์แบบโดยเฉพาะอย่างยิ่งหากคุณสามารถเลือกเงื่อนไขที่มีความเกี่ยวข้องกับความหมายของปัญหาได้ (เช่นฟังก์ชั่นการเปรียบเทียบอาจตั้งค่าสถานะดังนั้นjne
จะต้องดำเนินการหากไม่เท่ากับ)
char
) เพื่อเป็นสัญญาณหรือศูนย์ขยายเป็น 32 หรือ 64 บิตนี่ไม่ใช่เหตุผลอันสมควร; การใช้movzx
หรือmovsx
เพื่อหลีกเลี่ยงการชะลอการลงทะเบียนบางส่วนเป็นเรื่องปกติใน x86 asm ที่ทันสมัย ในความเป็นจริงเสียงดังกราว / LLVM แล้วทำให้รหัสที่ขึ้นอยู่กับส่วนขยายที่ไม่มีเอกสารไป x86-64 System V เรียกประชุม: args แคบกว่า 32 บิตจะเข้าสู่ระบบหรือศูนย์ขยายไปถึง 32 บิตโดยผู
คุณสามารถจัดทำเอกสาร / อธิบายส่วนขยายถึง 64 บิตโดยการเขียนuint64_t
หรือint64_t
ในต้นแบบของคุณหากคุณต้องการ เช่นเพื่อให้คุณสามารถใช้loop
คำสั่งซึ่งใช้ทั้ง 64 บิตของ RCX เว้นแต่ว่าคุณใช้คำนำหน้าขนาดที่อยู่เพื่อแทนที่ขนาดลงไปที่ 32 บิต ECX (ใช่จริงๆขนาดที่อยู่ไม่ได้ถูกดำเนินการขนาด)
โปรดทราบว่าlong
เป็นประเภท 32 บิตใน Windows 64 บิต ABI และLinux x32 ABIเท่านั้น เป็นที่ชัดเจนและสั้นกว่าชนิดuint64_t
unsigned long long
ของ Windows 32 บิต__fastcall
, แนะนำแล้วโดยคำตอบอื่น : args จำนวนเต็มในและecx
edx
x86-64 ระบบ V : ผ่าน args จำนวนมากในการลงทะเบียนและมีการลงทะเบียนการโทรที่ถูกปิดกั้นจำนวนมากซึ่งคุณสามารถใช้ได้โดยไม่ต้องใช้คำนำหน้า REX ที่สำคัญกว่านั้นคือมันถูกเลือกให้คอมไพเลอร์อินไลน์memcpy
หรือ memset rep movsb
ได้อย่างง่ายดาย: args จำนวนเต็ม / ตัวชี้ 6 ตัวแรกจะถูกส่งผ่านใน RDI, RSI, RDX, RCX, R8, R9
หากฟังก์ชั่นของคุณใช้lodsd
/ stosd
ภายในลูปที่รันrcx
เวลา (พร้อมloop
คำสั่ง) คุณสามารถพูดว่า "callable จาก C เช่นเดียวint foo(int *rdi, const int *rsi, int dummy, uint64_t len)
กับ x86-64 System V call Convention" ตัวอย่างเช่น: chromakey
32 บิต GCC regparm
: มีจำนวนเต็มใน EAX , ECX, EDX, ส่งคืนเป็น EAX (หรือ EDX: EAX) มีหาเรื่องครั้งแรกในการลงทะเบียนเช่นเดียวกับค่าตอบแทนที่ช่วยให้การเพิ่มประสิทธิภาพบางอย่างเช่นกรณีนี้ด้วยตัวอย่างโทรและต้นแบบที่มีคุณลักษณะฟังก์ชั่น และแน่นอนว่า AL / EAX นั้นพิเศษสำหรับคำแนะนำบางอย่าง
Linux x32 ABI ใช้ตัวชี้แบบ 32 บิตในโหมดยาวดังนั้นคุณสามารถบันทึกคำนำหน้า REX เมื่อทำการแก้ไขตัวชี้ ( ตัวอย่างเช่นการใช้ตัวพิมพ์ ) คุณยังคงสามารถใช้ขนาดที่อยู่ 64- บิตได้เว้นแต่คุณจะมีเลขจำนวนเต็มลบ 32- ศูนย์ในการลงทะเบียน (ดังนั้นมันจะเป็นค่าที่ไม่ได้ลงชื่อขนาดใหญ่ถ้าคุณทำ[rdi + rdx]
)
โปรดทราบว่าpush rsp
/ pop rax
คือ 2 ไบต์และเทียบเท่าmov rax,rsp
ดังนั้นคุณยังสามารถคัดลอกการลงทะเบียน 64- บิตเต็มใน 2 ไบต์
ret 16
; พวกเขาไม่ได้ปรากฏอยู่กลับผลักดันอาร์เรย์แล้ว/push rcx
ret
ผู้เรียกจะต้องทราบขนาดอาร์เรย์หรือบันทึก RSP ไว้ที่อื่นนอกสแต็กเพื่อค้นหาตัวเอง
ใช้การเข้ารหัสแบบสั้นกรณีพิเศษสำหรับ AL / AX / EAX และแบบฟอร์มสั้นอื่น ๆ และคำแนะนำแบบไบต์เดียว
ตัวอย่างสมมติโหมด 32/64 บิตโดยที่ขนาดตัวถูกดำเนินการเริ่มต้นคือ 32 บิต คำนำหน้าขนาดตัวถูกดำเนินการเปลี่ยนคำสั่งเป็น AX แทน EAX (หรือย้อนกลับในโหมด 16 บิต)
inc/dec
ลงทะเบียน (อื่น ๆ กว่า 8 บิต): /inc eax
dec ebp
(ไม่ใช่ x86-64: 0x4x
ไบต์ opcode ถูกนำมาใช้ใหม่เป็นคำนำหน้า REX ดังนั้นจึงinc r/m32
เป็นการเข้ารหัสเท่านั้น)
8 บิตinc bl
คือ 2 ไบต์ใช้inc r/m8
opcode + ModR / M ถูกดำเนินการเข้ารหัส ดังนั้นใช้inc ebx
เพื่อเพิ่มbl
ถ้ามันปลอดภัย (เช่นหากคุณไม่ต้องการผลลัพธ์ ZF ในกรณีที่ไบต์บนอาจไม่ใช่ศูนย์)
scasd
: e/rdi+=4
ต้องการให้ register register ไปยังหน่วยความจำที่สามารถอ่านได้ บางครั้งมีประโยชน์แม้ว่าคุณจะไม่สนใจผลลัพธ์ของธง (เช่นcmp eax,[rdi]
/ rdi+=4
) และในโหมด 64- บิตscasb
สามารถทำงานเป็น 1 ไบต์inc rdi
ถ้า lodsb หรือ stosb ไม่มีประโยชน์
xchg eax, r32
: นี่คือที่ 0x90 NOP xchg eax,eax
มาจาก: ตัวอย่าง: จัดเรียงรีจิสเตอร์อีก 3 ตัวโดยมีสองxchg
คำสั่งใน a cdq
/ idiv
loop สำหรับ GCD ใน 8 ไบต์ซึ่งคำสั่งส่วนใหญ่เป็นไบต์เดียวรวมถึงการใช้inc ecx
/ loop
แทนtest ecx,ecx
/jnz
cdq
: ลงชื่อขยาย EAX ไปยัง EDX: EAX, เช่นการคัดลอก EAX บิตสูงไปยัง EDX ทุกบิต หากต้องการสร้างศูนย์ที่รู้จักกันว่าไม่เป็นลบหรือรับ 0 / -1 เพื่อเพิ่ม / ย่อยหรือปิดบังด้วย x86 ประวัติศาสตร์บทเรียน: cltq
เทียบmovslq
และ AT & T กับ Intel cdqe
จำสำหรับเรื่องนี้และที่เกี่ยวข้อง
lodsb / d : like mov eax, [rsi]
/ rsi += 4
ไม่มีธงการอุดตัน (สมมติว่า DF มีความชัดเจนซึ่งมาตรฐานการประชุมที่จำเป็นต้องมีในรายการฟังก์ชั่น) นอกจากนี้ยัง stosb / d บางครั้ง scas และ movs / cmps บ่อยครั้งมากขึ้น
push
/ pop reg
. เช่นในโหมด 64- บิตpush rsp
/ pop rdi
เป็น 2 ไบต์ แต่mov rdi, rsp
ต้องการคำนำหน้า REX และ 3 ไบต์
xlatb
มีอยู่ แต่ไม่ค่อยมีประโยชน์ ตารางการค้นหาขนาดใหญ่เป็นสิ่งที่ควรหลีกเลี่ยง ฉันไม่เคยพบการใช้งานสำหรับ AAA / DAA หรือคำแนะนำในการบรรจุแบบ BCD หรือ 2-ASCII อื่น ๆ
1 ไบต์lahf
/ sahf
ไม่ค่อยมีประโยชน์ คุณสามารถ lahf
/ and ah, 1
เป็นทางเลือกแทนsetc ah
แต่โดยทั่วไปแล้วจะไม่มีประโยชน์
และสำหรับ CF โดยเฉพาะsbb eax,eax
จะต้องมีค่า 0 / -1 หรือแม้กระทั่งที่ไม่มีเอกสาร แต่รองรับ 1 ไบต์salc
(ตั้งค่า AL จาก Carry)ซึ่งทำได้อย่างมีประสิทธิภาพsbb al,al
โดยไม่ส่งผลกระทบต่อแฟล็ก (ถูกลบใน x86-64) ผมใช้ SALC ในAppreciation ผู้ใช้ท้าทาย # 1: เดนนิส♦
1-byte cmc
/ clc
/ stc
(flip ("complement"), clear หรือ set CF) ไม่ค่อยมีประโยชน์แม้ว่าฉันจะพบว่ามีการใช้งานcmc
ในการเพิ่มความแม่นยำเพิ่มเติมด้วยฐาน 10 ^ 9 หากต้องการตั้งค่า / ล้าง CF โดยไม่มีเงื่อนไขมักจะจัดให้สิ่งนั้นเกิดขึ้นโดยเป็นส่วนหนึ่งของคำสั่งอื่นเช่นxor eax,eax
ล้าง CF และ EAX ไม่มีคำแนะนำที่เทียบเท่าสำหรับแฟล็กเงื่อนไขอื่น ๆ เพียง DF (ทิศทางสตริง) และ IF (อินเตอร์รัปต์) ธงพกเป็นพิเศษสำหรับคำแนะนำมากมาย; กะตั้งมันadc al, 0
สามารถเพิ่มลงใน AL ใน 2 ไบต์และฉันกล่าวถึงก่อนหน้านี้ SALC ที่ไม่มีเอกสาร
std
/cld
ไม่ค่อยดูเหมือนมันคุ้มค่า โดยเฉพาะอย่างยิ่งในรหัส 32 บิตจะดีกว่าที่จะใช้dec
กับตัวชี้และ a mov
หรือแหล่งหน่วยความจำตัวถูกดำเนินการกับคำสั่ง ALU แทนการตั้งค่า DF ดังนั้นlodsb
/ stosb
ไปลงแทนที่จะขึ้น โดยปกติหากคุณต้องการลงทั้งหมดคุณยังคงมีตัวชี้อื่น ๆ เพิ่มขึ้นดังนั้นคุณต้องมีมากกว่าหนึ่งตัวstd
และcld
ในทั้งฟังก์ชันเพื่อใช้lods
/ stos
สำหรับทั้งคู่ ให้ใช้คำสั่งสตริงแทนทิศทางที่สูงขึ้น (ข้อกำหนดการโทรมาตรฐานรับประกัน DF = 0 ในรายการฟังก์ชันดังนั้นคุณสามารถสมมติได้ฟรีโดยไม่ต้องใช้cld
)
ในต้นฉบับ 8086, ขวานเป็นคนที่พิเศษมาก: คำแนะนำชอบlodsb
/ stosb
, cbw
, mul
/ div
และอื่น ๆ ใช้งานได้โดยปริยาย ยังคงเป็นกรณีของแน่นอน; x86 ปัจจุบันยังไม่ได้ลด opcodes ใด ๆ ของ 8086 (อย่างน้อยก็ไม่ได้มีเอกสารที่เป็นทางการ) แต่ภายหลังซีพียูได้เพิ่มคำแนะนำใหม่ที่ให้วิธีการที่ดีขึ้น / มีประสิทธิภาพมากขึ้นโดยไม่ต้องคัดลอกหรือสลับไปยัง AX ก่อน (หรือถึง EAX ในโหมด 32 บิต)
เช่น 8086 ขาดการเพิ่มเติมในภายหลังเช่นmovsx
/ movzx
เพื่อโหลดหรือย้าย + เครื่องหมายขยายหรือ 2 และ 3 ตัวถูกดำเนินการimul cx, bx, 1234
ที่ไม่ได้ผลครึ่งปีสูงและไม่มีตัวถูกดำเนินการโดยปริยาย
นอกจากนี้8086 ของคอขวดหลักคือการเรียนการสอนสามารถดึงข้อมูลเพื่อเพิ่มประสิทธิภาพสำหรับรหัสขนาดเป็นสิ่งสำคัญสำหรับการทำงานกลับมาแล้ว นักออกแบบ ISA 8086 (Stephen Morse)ใช้พื้นที่การเข้ารหัส opcode เป็นจำนวนมากในกรณีพิเศษสำหรับ AX / AL รวมถึง opcodes AX / AL พิเศษสำหรับ E-AXU สำหรับคำแนะนำ ALU พื้นฐานทันทีเพียงแค่ opcode + ทันที ไม่มี ModR / M ไบต์ 2 ไบต์add/sub/and/or/xor/cmp/test/... AL,imm8
หรือAX,imm16
หรือ (ในโหมด 32 EAX,imm32
บิต)
แต่ไม่มีกรณีพิเศษEAX,imm8
ดังนั้นการเข้ารหัส ModR / M ปกติของadd eax,4
จึงสั้นกว่า
สมมุติว่าถ้าคุณจะทำงานกับข้อมูลบางอย่างคุณจะต้องใช้มันใน AX / AL ดังนั้นการสลับการลงทะเบียนกับ AX นั้นเป็นสิ่งที่คุณอาจต้องการทำบางทีอาจจะมากกว่าการคัดลอก register ไปยัง AX ด้วยซ้ำmov
.
ทุกอย่างเกี่ยวกับการเข้ารหัสคำสั่ง 8086 รองรับกระบวนทัศน์นี้ตั้งแต่คำสั่งเช่นlodsb/w
ไปจนถึงการเข้ารหัสกรณีพิเศษทั้งหมดสำหรับ EAX ทันทีจนถึงการใช้งานโดยปริยายแม้กระทั่งการคูณ / หาร
อย่าถูกพาตัวไป ไม่ใช่การชนะโดยอัตโนมัติในการสลับทุกอย่างเป็น EAX โดยเฉพาะถ้าคุณต้องการใช้ทันทีด้วยการลงทะเบียนแบบ 32 บิตแทนที่จะเป็น 8 บิต หรือถ้าคุณต้องการ interleave การดำเนินงานกับหลายตัวแปรในการลงทะเบียนในครั้งเดียว หรือหากคุณกำลังใช้คำแนะนำกับการลงทะเบียน 2 รายการไม่สามารถทำได้ทันที
แต่โปรดจำไว้เสมอ: ฉันกำลังทำอะไรที่จะสั้นลงใน EAX / AL หรือไม่? ฉันสามารถจัดเรียงใหม่เพื่อให้ฉันมีสิ่งนี้ในอัลหรือฉันกำลังใช้ประโยชน์จากอัลที่ดีกว่ากับสิ่งที่ฉันใช้มันไปแล้ว
ผสมผสานการทำงาน 8 บิตและ 32 บิตอย่างอิสระเพื่อใช้ประโยชน์เมื่อใดก็ตามที่ปลอดภัย (คุณไม่จำเป็นต้องดำเนินการลงทะเบียนเต็มรูปแบบหรืออะไรก็ตาม)
cdq
มีประโยชน์สำหรับdiv
ความต้องการเป็นศูนย์edx
ในหลายกรณี
cdq
ก่อนที่จะลงนามdiv
หากคุณรู้ว่าเงินปันผลของคุณต่ำกว่า 2 ^ 31 (เช่นไม่เป็นลบเมื่อถือว่าเป็นลงชื่อ) หรือถ้าคุณใช้มันก่อนที่จะตั้งค่าeax
ที่อาจมีขนาดใหญ่ โดยปกติ (นอก code-golf) คุณจะใช้cdq
เป็นค่าติดตั้งidiv
และxor edx,edx
ก่อนdiv
fastcall
แบบแผนแพลตฟอร์ม x86 มีการเรียกประชุมจำนวนมาก คุณควรใช้ผู้ที่ผ่านพารามิเตอร์ในการลงทะเบียน บน x86_64 พารามิเตอร์สองสามตัวแรกจะถูกส่งผ่านไปยังการลงทะเบียนดังนั้นจึงไม่มีปัญหา บนแพลตฟอร์ม 32 บิตการเรียกใช้เริ่มต้น ( cdecl
) ส่งผ่านพารามิเตอร์ในสแต็กซึ่งไม่ดีสำหรับการเล่นกอล์ฟการเข้าถึงพารามิเตอร์บนสแต็กต้องใช้คำแนะนำที่ยาว
เมื่อใช้fastcall
บนแพลตฟอร์ม 32 บิต 2 พารามิเตอร์แรกมักจะผ่านในและecx
edx
หากฟังก์ชันของคุณมี 3 พารามิเตอร์คุณอาจลองนำไปใช้กับแพลตฟอร์ม 64 บิต
ต้นแบบฟังก์ชัน C สำหรับfastcall
การประชุม (นำมาจากคำตอบตัวอย่างนี้ ):
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU
0100 81C38000 ADD BX,0080
0104 83EB80 SUB BX,-80
ให้เพิ่ม -128 แทนการลบ 128
< 128
เป็น<= 127
ลดขนาดของตัวถูกดำเนินการทันทีcmp
หรือgcc มักชอบจัดเรียงใหม่ เปรียบเทียบเพื่อลดขนาดแม้ว่าจะไม่ใช่ -129 กับ -128
mul
(จากนั้นinc
/ dec
เพื่อรับ +1 / -1 เช่นเดียวกับศูนย์)คุณสามารถเป็นศูนย์ eax และ edx โดยคูณด้วยศูนย์ในการลงทะเบียนที่สาม
xor ebx, ebx ; 2B ebx = 0
mul ebx ; 2B eax=edx = 0
inc ebx ; 1B ebx=1
จะส่งผลให้ EAX, EDX และ EBX ทั้งหมดเป็นศูนย์ในเวลาเพียงสี่ไบต์ คุณสามารถเป็นศูนย์ EAX และ EDX ในสามไบต์:
xor eax, eax
cdq
แต่จากจุดเริ่มต้นนั้นคุณจะไม่สามารถลงทะเบียน zeroed ครั้งที่ 3 ในอีกหนึ่งไบต์หรือลงทะเบียน +1 หรือ -1 ในอีก 2 ไบต์ ให้ใช้เทคนิคของ mul แทน
ตัวอย่างเช่นกรณีการใช้งาน: เชื่อมโยงตัวเลขฟีโบนักชีในไบนารี
โปรดทราบว่าหลังจากLOOP
วนรอบเสร็จสิ้น ECX จะเป็นศูนย์และสามารถใช้เป็นศูนย์ EDX และ EAX คุณไม่จำเป็นต้องสร้างศูนย์แรกด้วยxor
เสมอไป
เราสามารถสันนิษฐานได้ว่าซีพียูอยู่ในสถานะเริ่มต้นที่เป็นที่รู้จักและจัดทำเป็นเอกสารตามแพลตฟอร์มและระบบปฏิบัติการ
ตัวอย่างเช่น:
DOS http://www.fysnet.net/yourhelp.htm
Linux x86 ELF http://asm.sourceforge.net/articles/startup.html
_start
ในการเข้า ใช่มันเป็นเกมที่ยุติธรรมที่จะใช้ประโยชน์จากสิ่งนั้นถ้าคุณกำลังเขียนโปรแกรมแทนที่จะเป็นฟังก์ชั่น ฉันไม่ได้ในสุดขีด Fibonacci (ในการปฏิบัติการเชื่อมโยงแบบไดนามิก, ld.so ก่อนจะวิ่งกระโดดที่คุณ_start
และไม่ขยะลาในทะเบียน แต่คงเป็นเพียงรหัสของคุณ.)
ในการเพิ่มหรือลบ 1 ให้ใช้หนึ่งไบต์inc
หรือdec
คำสั่งที่เล็กกว่าคำสั่งเพิ่มและคำสั่งย่อยแบบหลายไบต์
inc/dec r32
พร้อมหมายเลขลงทะเบียนที่เข้ารหัสใน opcode ดังนั้นinc ebx
เป็น 1 ไบต์ แต่inc bl
เป็น 2 ยังคงมีขนาดเล็กกว่าของหลักสูตรสำหรับการลงทะเบียนอื่นที่ไม่ใช่add bl, 1
al
นอกจากนี้โปรดทราบว่าinc
/ dec
ปล่อย CF ไม่ได้รับการแก้ไข แต่อัพเดตธงอื่น ๆ
lea
สำหรับคณิตศาสตร์นี่อาจเป็นหนึ่งในสิ่งแรก ๆ ที่เรียนรู้เกี่ยวกับ x86 แต่ฉันปล่อยไว้ที่นี่เพื่อเป็นการเตือน lea
สามารถใช้ในการคูณด้วย 2, 3, 4, 5, 8, หรือ 9 และเพิ่มออฟเซ็ต
ตัวอย่างเช่นการคำนวณebx = 9*eax + 3
ในคำสั่งเดียว (ในโหมด 32 บิต):
8d 5c c0 03 lea 0x3(%eax,%eax,8),%ebx
นี่มันไม่มีออฟเซ็ต:
8d 1c c0 lea (%eax,%eax,8),%ebx
ว้าว! แน่นอนlea
สามารถใช้ในการทำคณิตศาสตร์เช่นebx = edx + 8*eax + 3
การคำนวณการจัดทำดัชนีอาร์เรย์
lea eax, [rcx + 13]
เป็นรุ่นที่ไม่มีส่วนเสริมสำหรับโหมด 64 บิต ขนาดตัวถูกดำเนินการ 32 บิต (สำหรับผลลัพธ์) และขนาดที่อยู่ 64 บิต (สำหรับอินพุต)
คำสั่งวนซ้ำและสตริงมีขนาดเล็กกว่าลำดับการเรียนการสอนทางเลือก ส่วนใหญ่จะเป็นประโยชน์loop <label>
ซึ่งมีขนาดเล็กกว่าสองคำแนะนำลำดับdec ECX
และjnz <label>
และlodsb
มีขนาดเล็กกว่าและmov al,[esi]
inc si
mov
ขนาดเล็กลงในทะเบียนที่ต่ำกว่าทันทีหากคุณรู้ว่าบิตส่วนบนของรีจิสเตอร์เป็น 0 คุณสามารถใช้คำสั่งสั้นลงเพื่อย้ายรีจิสเตอร์เข้าสู่รีจิสเตอร์ล่างทันที
b8 0a 00 00 00 mov $0xa,%eax
กับ
b0 0a mov $0xa,%al
push
/ pop
สำหรับ imm8 ถึงศูนย์บิตบนมอบเครดิตให้กับ Peter Cordes xor
/ mov
คือ 4 ไบต์ แต่push
/ pop
เป็น 3 เท่านั้น!
6a 0a push $0xa
58 pop %eax
mov al, 0xa
เป็นสิ่งที่ดีถ้าคุณไม่ต้องการขยายศูนย์เต็ม reg แต่ถ้าคุณทำเช่นนั้น xor / mov คือ 4 ไบต์เทียบกับ 3 สำหรับการกด imm8 / pop หรือlea
จากค่าคงที่อื่นที่ทราบ ซึ่งอาจเป็นประโยชน์เมื่อใช้ร่วมกับmul
การลงทะเบียน 3 ศูนย์ใน 4 ไบต์หรือcdq
หากคุณต้องการค่าคงที่จำนวนมาก
[0x80..0xFF]
ซึ่งไม่สามารถแทนค่าได้เป็น imm8 แบบขยายสัญญาณ หรือถ้าคุณอยู่แล้วทราบไบต์บนเช่นmov cl, 0x10
หลังจากloop
การเรียนการสอนเพราะวิธีเดียวที่จะไม่กระโดดคือเมื่อมันทำloop
rcx=0
(ฉันเดาว่าคุณพูดแบบนี้ แต่ตัวอย่างของคุณใช้xor
) คุณสามารถใช้ไบต์ต่ำของการลงทะเบียนสำหรับสิ่งอื่นตราบใดที่สิ่งอื่นทำให้มันกลับเป็นศูนย์ (หรืออะไรก็ตาม) เมื่อคุณทำเสร็จแล้ว เช่นโปรแกรม Fibonacci ของฉันยังคง-1024
อยู่ใน ebx และใช้ bl
xchg eax, r32
) เช่นmov bl, 10
/ dec bl
/ jnz
เพื่อให้รหัสของคุณไม่สนใจไบต์สูงของ RBX
หลังจากคำแนะนำเกี่ยวกับการคำนวณทางคณิตศาสตร์มากมายตั้งค่าสถานะพกพา (ไม่ได้ลงชื่อ) และตั้งค่าสถานะโอเวอร์โฟลว์ (ลงนาม) โดยอัตโนมัติ ( ข้อมูลเพิ่มเติม ) การตั้งค่าสถานะการตั้งค่าสถานะและการตั้งค่าสถานะเป็นศูนย์หลังจากการดำเนินการทางคณิตศาสตร์และตรรกะมากมาย สามารถใช้สำหรับการแยกย่อยตามเงื่อนไข
ตัวอย่าง:
d1 f8 sar %eax
ZF ถูกกำหนดโดยคำสั่งนี้ดังนั้นเราจึงสามารถใช้มันเพื่อการแยกทางแบบมีเงื่อนไข
test al,1
นั้น คุณมักจะไม่ได้รับฟรี (หรือand al,1
เพื่อสร้างจำนวนเต็ม 0/1 ขึ้นอยู่กับเลขคี่ / คู่)
test
/ cmp
" ดังนั้นนั่นจะเป็นมือใหม่ที่ค่อนข้างดี x86 แต่ก็ยังคุ้มค่ากับการลงคะแนน
นี่ไม่ใช่เฉพาะ x86 แต่เป็นเคล็ดลับการประกอบเริ่มต้นที่ใช้กันอย่างแพร่หลาย หากคุณรู้ว่าขณะที่ลูปจะทำงานอย่างน้อยหนึ่งครั้งให้เขียนลูปเป็นลูปที่ทำในขณะที่การตรวจสอบสภาพลูปที่ปลายมักบันทึกคำสั่งการกระโดดแบบ 2 ไบต์ ในกรณีพิเศษคุณอาจจะสามารถใช้งานloop
ได้
do{}while()
สำนวนธรรมชาติวนซ้ำในแอสเซมบลี (โดยเฉพาะอย่างยิ่งสำหรับประสิทธิภาพ) โปรดทราบว่า 2 ไบต์jecxz
/ jrcxz
ก่อนที่ลูปจะทำงานได้ดีมากloop
ในการจัดการ "ต้องใช้เวลาเป็นศูนย์" เคส "อย่างมีประสิทธิภาพ" (บน CPU ที่หายากซึ่งloop
ไม่ได้ช้า) jecxz
ยังสามารถใช้งานได้ในลูปเพื่อใช้ awhile(ecx){}
โดยมีjmp
ที่ด้านล่าง
ระบบวี x86 ใช้สแต็คและระบบวีใช้ x86-64 rdi
, rsi
, rdx
, rcx
ฯลฯ สำหรับป้อนพารามิเตอร์และrax
เป็นค่าตอบแทน แต่มันเป็นอย่างดีที่เหมาะสมที่จะใช้เรียกประชุมของคุณเอง __fastcallใช้ecx
และedx
เป็นพารามิเตอร์การป้อนข้อมูลและคอมไพเลอร์อื่น ๆ / ระบบปฏิบัติการที่ใช้การประชุมของตัวเอง ใช้สแต็กและสิ่งที่ลงทะเบียนเป็นอินพุต / เอาต์พุตเมื่อสะดวก
ตัวอย่าง: ตัวนับไบต์ซ้ำโดยใช้หลักการเรียกที่ชาญฉลาดสำหรับโซลูชัน 1 ไบต์
Meta: การเขียนป้อนข้อมูลเพื่อลงทะเบียน , การเขียนออกไปลงทะเบียน
แหล่งข้อมูลอื่น ๆ : บันทึกของ Agner Fog เกี่ยวกับการเรียกประชุม
int 0x80
ต้องมีการตั้งค่ามากมาย
int 0x80
ในรหัส 32 บิตหรือรหัสsyscall
64 บิตที่จะเรียกใช้sys_write
เป็นวิธีที่ดีเท่านั้น มันเป็นสิ่งที่ฉันใช้สำหรับสุดขีด Fibonacci ในรหัส 64 บิตเพื่อให้คุณสามารถ__NR_write = 1 = STDOUT_FILENO
mov eax, edi
หรือถ้าจำนวนไบต์บนของ EAX เป็นศูนย์จะเป็นmov al, 4
รหัส 32 บิต คุณสามารถcall printf
หรือputs
ฉันเดาและเขียนคำตอบ "x86 asm สำหรับ Linux + glibc" ฉันคิดว่ามันสมเหตุสมผลที่จะไม่นับพื้นที่เข้าร่วม PLT หรือ GOT หรือรหัสห้องสมุดเอง
char*buf
และสร้างสตริงในนั้นด้วยการจัดรูปแบบด้วยตนเอง เช่นนี้(เหมาะสำหรับความเร็วอย่างเชื่องช้า) asm FizzBuzzที่ฉันได้รับข้อมูลสตริงเข้าสู่การลงทะเบียนแล้วเก็บไว้ด้วยmov
เพราะสตริงนั้นสั้นและยาวคงที่
CMOVcc
และการตั้งเงื่อนไขSETcc
นี่เป็นคำเตือนเพิ่มเติมให้กับฉันเอง แต่มีคำแนะนำการตั้งค่าตามเงื่อนไขและมีคำแนะนำการย้ายตามเงื่อนไขอยู่ในโปรเซสเซอร์ P6 (Pentium Pro) หรือใหม่กว่า มีคำแนะนำมากมายที่ขึ้นอยู่กับการตั้งค่าสถานะหนึ่งใน EFLAGS
cmov
มี opcode ขนาด 2 ไบต์ ( 0F 4x +ModR/M
) ดังนั้นจึงมีขนาดต่ำสุด 3 ไบต์ แต่แหล่งที่มาคือ r / m32 ดังนั้นคุณสามารถโหลดแบบมีเงื่อนไขใน 3 ไบต์ อื่น ๆ กว่ากิ่งจะเป็นประโยชน์ในกรณีมากกว่าsetcc
cmovcc
ยังคงพิจารณาชุดคำสั่งทั้งหมดไม่ใช่แค่พื้นฐาน 386 คำแนะนำ (แม้ว่าคำสั่ง SSE2 และ BMI / BMI2 มีขนาดใหญ่มากซึ่งไม่ค่อยมีประโยชน์ rorx eax, ecx, 32
6 ไบต์ยาวกว่า mov + ror ดีสำหรับการแสดงไม่ใช่กอล์ฟเว้นแต่ว่า POPCNT หรือ PDEP จะช่วยประหยัดเกาะได้มากมาย)
setcc
ขอบคุณฉันได้เพิ่ม
jmp
ไบต์ด้วยการจัดเรียง if / then มากกว่า if / then / elseนี่เป็นพื้นฐานที่สำคัญมากเพียงแค่คิดว่าฉันจะโพสต์สิ่งนี้เป็นสิ่งที่ควรพิจารณาเมื่อเล่นกอล์ฟ ยกตัวอย่างเช่นพิจารณารหัสตรงไปตรงมาต่อไปนี้เพื่อถอดรหัสตัวเลขฐานสิบหก:
cmp $'A', %al
jae .Lletter
sub $'0', %al
jmp .Lprocess
.Lletter:
sub $('A'-10), %al
.Lprocess:
movzbl %al, %eax
...
สิ่งนี้สามารถย่อให้สั้นลงได้สองไบต์โดยให้ตัวพิมพ์เล็กและใหญ่ตกลงมาเป็นตัวพิมพ์เล็ก:
cmp $'A', %al
jb .digit
sub $('A'-'0'-10), %eax
.digit:
sub $'0', %eax
movzbl %al, %eax
...
sub
เวลาแฝงที่เพิ่มบนเส้นทางวิกฤติสำหรับกรณีหนึ่งไม่ได้เป็นส่วนหนึ่งของห่วงโซ่อ้างอิงแบบวนซ้ำ (เช่นที่นี่ซึ่งแต่ละอินพุตหลักมีความเป็นอิสระ ) แต่ฉันเดาว่า +1 BTW ตัวอย่างของคุณมีการเพิ่มประสิทธิภาพพลาดเฉพาะกิจการ: ถ้าคุณกำลังจะต้องมีmovzx
ที่สิ้นสุดอยู่แล้วจากนั้นใช้sub $imm, %al
ไม่ได้ EAX ที่จะใช้ประโยชน์จากการที่ไม่มีการเข้ารหัส modrm 2 op $imm, %al
ไบต์ของ
cmp
โดยการทำsub $'A'-10, %al
; jae .was_alpha
; add $('A'-10)-'0'
. (ฉันคิดว่าฉันมีเหตุผลที่ถูกต้อง) โปรดทราบว่า'A'-10 > '9'
ไม่มีความกำกวม การลบการแก้ไขสำหรับตัวอักษรจะตัดทศนิยมหลัก ดังนั้นนี่จึงปลอดภัยถ้าเราสมมติว่าอินพุตของเรานั้นเป็นเลขฐานสิบหกที่ถูกต้องเช่นเดียวกับที่คุณทำ
คุณสามารถดึงข้อมูลวัตถุตามลำดับจากสแต็กได้โดยตั้งค่า esi เป็น esp และดำเนินการตามลำดับของ lodsd / xchg reg, eax
pop eax
/ pop edx
/ ... หากคุณจำเป็นต้องปล่อยให้พวกเขาในกองคุณสามารถpush
ให้พวกเขาทั้งหมดกลับมาหลังจากที่จะเรียกคืน ESP ยังคง 2 mov esi,esp
ไบต์ต่อวัตถุโดยไม่จำเป็นต้อง หรือคุณหมายถึงวัตถุ 4 ไบต์ในรหัส 64 บิตที่pop
จะได้รับ 8 ไบต์? BTW คุณสามารถใช้pop
เพื่อวนลูปบัฟเฟอร์ด้วยประสิทธิภาพที่ดีกว่าlodsd
เช่นสำหรับการเพิ่มความแม่นยำสูงใน Extreme Fibonacci
ในการคัดลอกการลงทะเบียน 64- บิตให้ใช้push rcx
; pop rdx
แทนที่จะเป็น mov
3
ขนาดตัวถูกดำเนินการเริ่มต้นของ push / pop คือ 64- บิตโดยไม่จำเป็นต้องมีคำนำหน้า REX
51 push rcx
5a pop rdx
vs.
48 89 ca mov rdx,rcx
(คำนำหน้าขนาดตัวถูกดำเนินการสามารถแทนที่ขนาด push / pop เป็น 16 บิต แต่ขนาดตัวถูกดำเนินการ push-pop 32 บิตไม่สามารถเข้ารหัสในโหมด 64 บิตแม้กับ REX.W = 0)
หากการลงทะเบียนอย่างใดอย่างหนึ่งหรือทั้งสองอย่างเป็นr8
.. r15
ให้ใช้mov
เพราะการพุชและ / หรือป๊อปจะต้องใช้คำนำหน้า REX กรณีที่เลวร้ายที่สุดสิ่งนี้จะเสียจริงถ้าทั้งคู่ต้องการคำนำหน้า REX เห็นได้ชัดว่าคุณควรหลีกเลี่ยง r8..r15 ต่อไปในรหัสกอล์ฟ
คุณสามารถให้แหล่งข้อมูลของคุณอ่านได้มากขึ้นในขณะที่พัฒนาด้วยแมโคร NASMนี้ เพียงจำไว้ว่ามันทำตามขั้นตอนใน 8 ไบต์ด้านล่าง RSP (ในพื้นที่สีแดงใน x86-64 System V) แต่ภายใต้สภาวะปกติมันเป็นการแทนที่สำหรับ 64- บิตmov r64,r64
หรือmov r64, -128..127
; mov %1, %2 ; use this macro to copy 64-bit registers in 2 bytes (no REX prefix)
%macro MOVE 2
push %2
pop %1
%endmacro
ตัวอย่าง:
MOVE rax, rsi ; 2 bytes (push + pop)
MOVE rbp, rdx ; 2 bytes (push + pop)
mov ecx, edi ; 2 bytes. 32-bit operand size doesn't need REX prefixes
MOVE r8, r10 ; 4 bytes, don't use
mov r8, r10 ; 3 bytes, REX prefix has W=1 and the bits for reg and r/m being high
xchg eax, edi ; 1 byte (special xchg-with-accumulator opcodes)
xchg rax, rdi ; 2 bytes (REX.W + that)
xchg ecx, edx ; 2 bytes (normal xchg + modrm)
xchg rcx, rdx ; 3 bytes (normal REX + xchg + modrm)
xchg
ส่วนหนึ่งของตัวอย่างเป็นเพราะบางครั้งคุณจำเป็นต้องได้รับค่าลงใน EAX หรือ Rax และไม่เกี่ยวกับการดูแลรักษาชุดเก่า แต่ดัน / ป๊อปไม่ได้ช่วยให้คุณแลกเปลี่ยนได้จริง
push 200; pop edx
- 3 ไบต์สำหรับการเริ่มต้น