เคล็ดลับสำหรับการเล่นกอล์ฟในรหัสเครื่อง x86 / x64


27

ฉันสังเกตเห็นว่าไม่มีคำถามดังกล่าวดังนั้นที่นี่:

คุณมีเคล็ดลับทั่วไปสำหรับการเล่นกอล์ฟในรหัสเครื่องหรือไม่? หากเคล็ดลับนี้ใช้กับสภาพแวดล้อมบางอย่างหรือการประชุมที่โทรมาเท่านั้นโปรดระบุในคำตอบของคุณ

กรุณาเพียงหนึ่งเคล็ดลับต่อคำตอบ (ดูที่นี่ )

คำตอบ:


11

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/ popdefault 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 อย่างมีประสิทธิภาพ


นอกจากนี้เพื่อเริ่มต้นการลงทะเบียนด้วยค่าเล็ก ๆ (8 บิต) นอกเหนือจาก 0: ใช้เช่นpush 200; pop edx- 3 ไบต์สำหรับการเริ่มต้น
Anatolyg

2
BTW เพื่อเริ่มต้นการลงทะเบียนเพื่อ -1 ใช้decเช่นxor eax, eax; dec eax
Anatolyg

@anatolyg: 200 เป็นตัวอย่างที่ไม่ดี แต่ไม่เหมาะกับการลงชื่อเข้าใช้แบบขยาย - imm8 แต่ใช่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
Peter Cordes

10

ในหลายกรณีคำแนะนำที่อิงกับแอคคูเลเตอร์ (เช่นคำสั่งที่ใช้(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เหมือนในคำตอบของความเข้มของสีที่สำคัญของฉัน
ปีเตอร์ Cordes

8

เลือกแบบแผนการโทรของคุณเพื่อวางส่วนที่คุณต้องการ

ภาษาของคำตอบของคุณคือ asm (จริง ๆ แล้วรหัสเครื่อง) ดังนั้นให้ถือว่าเป็นส่วนหนึ่งของโปรแกรมที่เขียนด้วย asm ไม่ใช่ C-compiled-for-x86 ฟังก์ชั่นของคุณไม่จำเป็นต้องโทรออกได้ง่ายจาก C กับแบบแผนการโทรมาตรฐานใด ๆ นั่นเป็นโบนัสที่ดีถ้ามันไม่ทำให้คุณเสียค่าใช้จ่ายเพิ่ม

ในโปรแกรม asm ล้วนเป็นเรื่องปกติสำหรับฟังก์ชั่นตัวช่วยบางอย่างที่จะใช้แบบแผนการโทรที่สะดวกสำหรับพวกเขาและสำหรับผู้โทร ฟังก์ชั่นดังกล่าวจัดทำเอกสารแผนการประชุมที่เรียกว่า

ในชีวิตจริงแม้โปรแกรม asm จะทำ (ฉันคิดว่า) มีแนวโน้มที่จะใช้แบบแผนการเรียกที่สอดคล้องกันสำหรับฟังก์ชั่นส่วนใหญ่ ใน code-golf คุณกำลังปรับอึออกจากฟังก์ชั่นเดียวดังนั้นจึงเป็นสิ่งสำคัญ / พิเศษ


เพื่อทดสอบการทำงานของคุณจากโปรแกรม C, สามารถเขียนเสื้อคลุมที่ args ทำให้ในสถานที่ที่เหมาะสมจะช่วยประหยัด / คืนค่าลงทะเบียนพิเศษใด ๆ ที่คุณข่มขี่และทำให้ค่าส่งกลับเข้าไปe/raxถ้ามันไม่ได้มีอยู่แล้ว


ข้อ จำกัด ของสิ่งที่สมเหตุสมผล: สิ่งใดก็ตามที่ไม่กำหนดภาระที่ไม่สมเหตุสมผลสำหรับผู้โทร:

  • ESP / RSP จะต้องป้องกันการโทร เลขจำนวนเต็มอื่น ๆ เป็นเกมที่ยุติธรรม (RBP และ RBX มักจะถูกสงวนไว้ในการประชุมปกติ แต่คุณสามารถปิดกั้นทั้งสอง)
  • การหาเรื่องใด ๆ ในการลงทะเบียนใด ๆ (ยกเว้น RSP) นั้นสมเหตุสมผล แต่การขอให้ผู้โทรคัดลอกข้อโต้แย้งเดียวกันไปยังหลาย ๆ การลงทะเบียนไม่ได้
  • การกำหนดให้ 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แต่นั่นไม่ใช่ "สมเหตุสมผล" นั่นไม่ได้มีประสิทธิภาพเท่ากับการโทร / รับดังนั้นจึงไม่ใช่สิ่งที่คุณจะพบได้ในรหัสจริง
  • การปิดกั้นหน่วยความจำไม่ จำกัด ด้านบน RSP นั้นไม่สมเหตุสมผล แต่การปิดกั้นฟังก์ชั่นของคุณอยู่บนสแต็กนั้นอนุญาตให้ใช้ในการประชุมปกติได้ x64 Windows ต้องการพื้นที่แชโดว์ 32 ไบต์ด้านบนที่อยู่ผู้ส่งในขณะที่ x86-64 System V ให้พื้นที่สีแดง 128 ไบต์ใต้ RSP ดังนั้นทั้งสองอย่างนี้สมเหตุสมผล (หรือแม้กระทั่งพื้นที่สีแดงขนาดใหญ่กว่ามากโดยเฉพาะในโปรแกรมสแตนด์อะโลนมากกว่าฟังก์ชั่น)

กรณีชายแดน: การเขียนฟังก์ชั่นที่ผลิตลำดับในอาร์เรย์ที่กำหนด 2 องค์ประกอบแรกเป็น args ฉันเลือกให้ผู้โทรจัดเก็บการเริ่มต้นของลำดับลงในอาร์เรย์และเพียงแค่ส่งตัวชี้ไปยังอาร์เรย์ นี่คือการดัดข้อกำหนดของคำถาม ผมถือว่าการ args บรรจุเข้าxmm0สำหรับmovlps [rdi], xmm0ซึ่งยังจะเป็นเรียกประชุมแปลก


ส่งคืนบูลีนใน FLAGS (รหัสเงื่อนไข)

การเรียกใช้ระบบ OS X ทำได้ ( CF=0หมายถึงไม่มีข้อผิดพลาด): มันถือว่าเป็นการปฏิบัติที่ไม่ถูกต้องหรือไม่ที่จะใช้การลงทะเบียนค่าสถานะเป็นค่าส่งคืนบูลีน? .

เงื่อนไขใด ๆ ที่สามารถตรวจสอบได้ด้วย JCC หนึ่งรายการนั้นสมเหตุสมผลอย่างสมบูรณ์แบบโดยเฉพาะอย่างยิ่งหากคุณสามารถเลือกเงื่อนไขที่มีความเกี่ยวข้องกับความหมายของปัญหาได้ (เช่นฟังก์ชั่นการเปรียบเทียบอาจตั้งค่าสถานะดังนั้นjneจะต้องดำเนินการหากไม่เท่ากับ)


ต้องการ args ที่แคบ (เช่น a 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_tunsigned long long


อนุสัญญาการโทรที่มีอยู่:

  • ของ Windows 32 บิต__fastcall, แนะนำแล้วโดยคำตอบอื่น : args จำนวนเต็มในและecxedx

  • 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 ไบต์


เมื่อความท้าทายขอให้คืนค่าอาร์เรย์คุณคิดว่าการคืนค่าในกองซ้อนนั้นสมเหตุสมผลหรือไม่ ฉันคิดว่านั่นคือสิ่งที่คอมไพเลอร์จะทำอย่างไรเมื่อคืนโครงสร้างตามค่า
qwr

@qwr: ไม่อนุสัญญาการโทรหลักผ่านตัวชี้ที่ซ่อนอยู่กับค่าที่ส่งคืน (อนุสัญญาบางข้อผ่าน / ส่งคืนโครงสร้างขนาดเล็กในการลงทะเบียน) C / C ++ คืนโครงสร้างตามค่าภายใต้ประทุนและดูจุดสิ้นสุดของวัตถุทำงานอย่างไรใน x86 ที่ระดับการประกอบ . โปรดทราบว่าการส่งผ่านอาร์เรย์ (ภายใน structs) จะคัดลอกลงในสแต็กสำหรับ x86-64 SysV: ชนิดข้อมูล C11 ประเภทใดที่เป็นอาร์เรย์ตาม AMD64 ABIแต่ Windows x64 ผ่านตัวชี้แบบ non-const
Peter Cordes

แล้วคุณคิดว่าสมเหตุสมผลหรือไม่ คุณนับ x86 ภายใต้กฎนี้codegolf.meta.stackexchange.com/a/8507/17360
qwr

1
@qwr: x86 ไม่ใช่ "ภาษาตามสแต็ก" x86 เป็นเครื่องที่ลงทะเบียนกับ RAMไม่ได้เป็นเครื่องสแต็ค เครื่องสแต็คเป็นเหมือนสัญลักษณ์ขัดเงาย้อนกลับเช่น x87 register fld / fld / faddp call-stack ของ x86 ไม่เหมาะกับรูปแบบนั้น: อนุสัญญาการโทรปกติทั้งหมดปล่อยให้ RSP ไม่ได้แก้ไขหรือ pop args ด้วยret 16; พวกเขาไม่ได้ปรากฏอยู่กลับผลักดันอาร์เรย์แล้ว/push rcx retผู้เรียกจะต้องทราบขนาดอาร์เรย์หรือบันทึก RSP ไว้ที่อื่นนอกสแต็กเพื่อค้นหาตัวเอง
Peter Cordes

การโทรจะผลักที่อยู่ของการเรียนการสอนหลังจากการโทรใน stack jmp ไปยังฟังก์ชั่นที่เรียกว่า ret ปรากฏที่อยู่จาก stack และ jmp ไปยังที่อยู่นั้น
RosLuP

7

ใช้การเข้ารหัสแบบสั้นกรณีพิเศษสำหรับ 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/m8opcode + 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/ idivloop สำหรับ 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: ทำไมการเข้ารหัสเหล่านี้มีอยู่

ในต้นฉบับ 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ในหลายกรณี
qwr

1
@qwr: ถูกต้องคุณสามารถละเมิดcdqก่อนที่จะลงนามdivหากคุณรู้ว่าเงินปันผลของคุณต่ำกว่า 2 ^ 31 (เช่นไม่เป็นลบเมื่อถือว่าเป็นลงชื่อ) หรือถ้าคุณใช้มันก่อนที่จะตั้งค่าeaxที่อาจมีขนาดใหญ่ โดยปกติ (นอก code-golf) คุณจะใช้cdqเป็นค่าติดตั้งidivและxor edx,edxก่อนdiv
Peter Cordes

5

ใช้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   

หรือใช้รูปแบบการโทรที่กำหนดเองอย่างสมบูรณ์เพราะคุณกำลังเขียนด้วย asm บริสุทธิ์ไม่จำเป็นต้องเขียนรหัสที่จะเรียกจาก C. การกลับมาของ booleans ใน FLAGS นั้นมักจะสะดวก
Peter Cordes

5

ลบ -128 แทนการเพิ่ม 128

0100 81C38000      ADD     BX,0080
0104 83EB80        SUB     BX,-80

ให้เพิ่ม -128 แทนการลบ 128


1
นอกจากนี้ยังใช้งานได้ในทิศทางอื่น: เพิ่ม -128 แทนย่อย 128 ข้อเท็จจริงที่สนุกสนาน: คอมไพเลอร์ทราบการเพิ่มประสิทธิภาพนี้และยังทำการปรับให้เหมาะสมที่เกี่ยวข้องของการเปลี่ยน< 128เป็น<= 127ลดขนาดของตัวถูกดำเนินการทันทีcmpหรือgcc มักชอบจัดเรียงใหม่ เปรียบเทียบเพื่อลดขนาดแม้ว่าจะไม่ใช่ -129 กับ -128
Peter Cordes

4

สร้าง 3 ศูนย์ด้วย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เสมอไป


1
นี่เป็นความสับสนเล็กน้อย คุณสามารถขยายหรือไม่
NoOneIsHere

@ ไม่มีใครที่นี่ฉันเชื่อว่าเขาต้องการตั้งค่าการลงทะเบียนสามรายการเป็น 0 รวมถึง EAX และ EDX
NieDzejkob

4

การลงทะเบียน CPU และแฟล็กอยู่ในสถานะเริ่มต้นที่ทราบ

เราสามารถสันนิษฐานได้ว่าซีพียูอยู่ในสถานะเริ่มต้นที่เป็นที่รู้จักและจัดทำเป็นเอกสารตามแพลตฟอร์มและระบบปฏิบัติการ

ตัวอย่างเช่น:

DOS http://www.fysnet.net/yourhelp.htm

Linux x86 ELF http://asm.sourceforge.net/articles/startup.html


1
กฎของรหัสกอล์ฟบอกว่ารหัสของคุณต้องใช้งานอย่างน้อยหนึ่งครั้ง ลินุกซ์เลือกที่จะเป็นศูนย์ Regs ทั้งหมด (ยกเว้น RSP) และสแต็คก่อนที่จะเข้าสู่ขั้นตอนการใช้พื้นที่สดแม้ว่า i386 และ x86-64 เอกสารระบบวี ABI กล่าวว่าพวกเขา "ไม่ได้กำหนด" _startในการเข้า ใช่มันเป็นเกมที่ยุติธรรมที่จะใช้ประโยชน์จากสิ่งนั้นถ้าคุณกำลังเขียนโปรแกรมแทนที่จะเป็นฟังก์ชั่น ฉันไม่ได้ในสุดขีด Fibonacci (ในการปฏิบัติการเชื่อมโยงแบบไดนามิก, ld.so ก่อนจะวิ่งกระโดดที่คุณ_startและไม่ขยะลาในทะเบียน แต่คงเป็นเพียงรหัสของคุณ.)
ปีเตอร์ Cordes

3

ในการเพิ่มหรือลบ 1 ให้ใช้หนึ่งไบต์incหรือdecคำสั่งที่เล็กกว่าคำสั่งเพิ่มและคำสั่งย่อยแบบหลายไบต์


โปรดทราบว่าโหมด 32 บิตมี 1 ไบต์inc/dec r32พร้อมหมายเลขลงทะเบียนที่เข้ารหัสใน opcode ดังนั้นinc ebxเป็น 1 ไบต์ แต่inc blเป็น 2 ยังคงมีขนาดเล็กกว่าของหลักสูตรสำหรับการลงทะเบียนอื่นที่ไม่ใช่add bl, 1 alนอกจากนี้โปรดทราบว่าinc/ decปล่อย CF ไม่ได้รับการแก้ไข แต่อัพเดตธงอื่น ๆ
Peter Cordes

1
2 สำหรับ +2 & -2 ใน x86
l4m2

3

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การคำนวณการจัดทำดัชนีอาร์เรย์


1
อาจคุ้มค่าที่จะกล่าวถึงว่าlea eax, [rcx + 13]เป็นรุ่นที่ไม่มีส่วนเสริมสำหรับโหมด 64 บิต ขนาดตัวถูกดำเนินการ 32 บิต (สำหรับผลลัพธ์) และขนาดที่อยู่ 64 บิต (สำหรับอินพุต)
Peter Cordes

3

คำสั่งวนซ้ำและสตริงมีขนาดเล็กกว่าลำดับการเรียนการสอนทางเลือก ส่วนใหญ่จะเป็นประโยชน์loop <label>ซึ่งมีขนาดเล็กกว่าสองคำแนะนำลำดับdec ECXและjnz <label>และlodsbมีขนาดเล็กกว่าและmov al,[esi]inc si


2

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หากคุณต้องการค่าคงที่จำนวนมาก
Peter Cordes

กรณีการใช้งานอื่นจะเป็นค่าคงที่จาก[0x80..0xFF]ซึ่งไม่สามารถแทนค่าได้เป็น imm8 แบบขยายสัญญาณ หรือถ้าคุณอยู่แล้วทราบไบต์บนเช่นmov cl, 0x10หลังจากloopการเรียนการสอนเพราะวิธีเดียวที่จะไม่กระโดดคือเมื่อมันทำloop rcx=0(ฉันเดาว่าคุณพูดแบบนี้ แต่ตัวอย่างของคุณใช้xor) คุณสามารถใช้ไบต์ต่ำของการลงทะเบียนสำหรับสิ่งอื่นตราบใดที่สิ่งอื่นทำให้มันกลับเป็นศูนย์ (หรืออะไรก็ตาม) เมื่อคุณทำเสร็จแล้ว เช่นโปรแกรม Fibonacci ของฉันยังคง-1024อยู่ใน ebx และใช้ bl
Peter Cordes

@PeterCordes ฉันได้เพิ่มเทคนิค push / pop ของคุณแล้ว
qwr

น่าจะเป็นคำตอบที่มีอยู่เกี่ยวกับค่าคงที่ซึ่งanatolyg ได้แนะนำไว้ในความคิดเห็นแล้ว ฉันจะแก้ไขคำตอบนั้น IMO คุณควรทำใหม่นี้เพื่อแนะนำให้ใช้ขนาดตัวถูกดำเนินการ 8 บิตสำหรับสิ่งอื่น ๆ (ยกเว้นxchg eax, r32) เช่นmov bl, 10/ dec bl/ jnzเพื่อให้รหัสของคุณไม่สนใจไบต์สูงของ RBX
Peter Cordes

@PeterCordes อืม ฉันยังไม่แน่ใจว่าเมื่อใดควรใช้ตัวถูกดำเนินการ 8 บิตดังนั้นฉันไม่แน่ใจว่าจะตอบอย่างไร
qwr

2

ธงมีการตั้งค่าคำแนะนำหลังจากที่หลาย ๆ

หลังจากคำแนะนำเกี่ยวกับการคำนวณทางคณิตศาสตร์มากมายตั้งค่าสถานะพกพา (ไม่ได้ลงชื่อ) และตั้งค่าสถานะโอเวอร์โฟลว์ (ลงนาม) โดยอัตโนมัติ ( ข้อมูลเพิ่มเติม ) การตั้งค่าสถานะการตั้งค่าสถานะและการตั้งค่าสถานะเป็นศูนย์หลังจากการดำเนินการทางคณิตศาสตร์และตรรกะมากมาย สามารถใช้สำหรับการแยกย่อยตามเงื่อนไข

ตัวอย่าง:

d1 f8                   sar    %eax

ZF ถูกกำหนดโดยคำสั่งนี้ดังนั้นเราจึงสามารถใช้มันเพื่อการแยกทางแบบมีเงื่อนไข


คุณเคยใช้ธงแพริตีเมื่อใด คุณรู้หรือไม่ว่ามันคือ xor แนวนอนของผลลัพธ์ 8 บิตต่ำใช่ไหม (ไม่ว่าจะเป็นตัวถูกดำเนินการขนาดใดก็ตามPF จะถูกตั้งค่าจากบิตต่ำ 8เท่านั้นดูเพิ่มเติมที่ ) ไม่ใช่เลขคู่ / เลขคี่ สำหรับการตรวจสอบ ZF หลังจากtest al,1นั้น คุณมักจะไม่ได้รับฟรี (หรือand al,1เพื่อสร้างจำนวนเต็ม 0/1 ขึ้นอยู่กับเลขคี่ / คู่)
Peter Cordes

อย่างไรก็ตามหากคำตอบนี้บอกว่า "ใช้แฟล็กที่ตั้งค่าไว้แล้วโดยคำแนะนำอื่น ๆ เพื่อหลีกเลี่ยงtest/ cmp" ดังนั้นนั่นจะเป็นมือใหม่ที่ค่อนข้างดี x86 แต่ก็ยังคุ้มค่ากับการลงคะแนน
Peter Cordes

@PeterCordes อืมฉันดูเหมือนจะเข้าใจผิดว่าเป็นเรื่องของความเท่าเทียมกัน ฉันยังคงตอบคำถามอื่นอยู่ ฉันจะแก้ไขคำตอบ และอย่างที่คุณอาจบอกได้ว่าฉันเป็นผู้เริ่มต้นที่จะช่วยให้เคล็ดลับขั้นพื้นฐาน
qwr

2

ใช้ลูป do-while แทนลูป do-while

นี่ไม่ใช่เฉพาะ x86 แต่เป็นเคล็ดลับการประกอบเริ่มต้นที่ใช้กันอย่างแพร่หลาย หากคุณรู้ว่าขณะที่ลูปจะทำงานอย่างน้อยหนึ่งครั้งให้เขียนลูปเป็นลูปที่ทำในขณะที่การตรวจสอบสภาพลูปที่ปลายมักบันทึกคำสั่งการกระโดดแบบ 2 ไบต์ ในกรณีพิเศษคุณอาจจะสามารถใช้งานloopได้


2
ที่เกี่ยวข้อง: ทำไมลูปมักจะรวบรวมอย่างนี้เสมอ อธิบายว่าทำไมdo{}while()สำนวนธรรมชาติวนซ้ำในแอสเซมบลี (โดยเฉพาะอย่างยิ่งสำหรับประสิทธิภาพ) โปรดทราบว่า 2 ไบต์jecxz/ jrcxzก่อนที่ลูปจะทำงานได้ดีมากloopในการจัดการ "ต้องใช้เวลาเป็นศูนย์" เคส "อย่างมีประสิทธิภาพ" (บน CPU ที่หายากซึ่งloopไม่ได้ช้า) jecxzยังสามารถใช้งานได้ในลูปเพื่อใช้ awhile(ecx){}โดยมีjmpที่ด้านล่าง
Peter Cordes

@ PeterCordes ที่เป็นคำตอบที่เขียนได้ดีมาก ฉันต้องการค้นหาวิธีใช้ในการกระโดดลงกลางวงในโปรแกรมกอล์ฟรหัส
qwr

ใช้ goto jmp และเยื้อง ... วน
ซ้ำ

2

ใช้วิธีการประชุมที่สะดวกสบาย

ระบบวี x86 ใช้สแต็คและระบบวีใช้ x86-64 rdi, rsi, rdx, rcxฯลฯ สำหรับป้อนพารามิเตอร์และraxเป็นค่าตอบแทน แต่มันเป็นอย่างดีที่เหมาะสมที่จะใช้เรียกประชุมของคุณเอง __fastcallใช้ecxและedxเป็นพารามิเตอร์การป้อนข้อมูลและคอมไพเลอร์อื่น ๆ / ระบบปฏิบัติการที่ใช้การประชุมของตัวเอง ใช้สแต็กและสิ่งที่ลงทะเบียนเป็นอินพุต / เอาต์พุตเมื่อสะดวก

ตัวอย่าง: ตัวนับไบต์ซ้ำโดยใช้หลักการเรียกที่ชาญฉลาดสำหรับโซลูชัน 1 ไบต์

Meta: การเขียนป้อนข้อมูลเพื่อลงทะเบียน , การเขียนออกไปลงทะเบียน

แหล่งข้อมูลอื่น ๆ : บันทึกของ Agner Fog เกี่ยวกับการเรียกประชุม


1
ในที่สุดฉันก็โพสต์คำตอบของฉันเองในคำถามนี้เกี่ยวกับการประชุมทางโทรศัพท์และสิ่งที่สมเหตุสมผลและไม่มีเหตุผล
Peter Cordes

@PeterCordes ไม่เกี่ยวข้องวิธีที่ดีที่สุดในการพิมพ์ใน x86 คืออะไร? จนถึงตอนนี้ฉันได้หลีกเลี่ยงความท้าทายที่ต้องใช้การพิมพ์ DOS ดูเหมือนว่าจะมีการขัดจังหวะที่มีประโยชน์สำหรับ I / O แต่ฉันวางแผนที่จะเขียนคำตอบแบบ 32/64 บิตเท่านั้น วิธีเดียวที่ฉันรู้ก็คือint 0x80ต้องมีการตั้งค่ามากมาย
qwr

ใช่int 0x80ในรหัส 32 บิตหรือรหัสsyscall64 บิตที่จะเรียกใช้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 หรือรหัสห้องสมุดเอง
Peter Cordes

1
ฉันมีแนวโน้มที่จะให้ผู้โทรผ่านchar*bufและสร้างสตริงในนั้นด้วยการจัดรูปแบบด้วยตนเอง เช่นนี้(เหมาะสำหรับความเร็วอย่างเชื่องช้า) asm FizzBuzzที่ฉันได้รับข้อมูลสตริงเข้าสู่การลงทะเบียนแล้วเก็บไว้ด้วยmovเพราะสตริงนั้นสั้นและยาวคงที่
Peter Cordes

1

ใช้การเคลื่อนไหวCMOVccและการตั้งเงื่อนไขSETcc

นี่เป็นคำเตือนเพิ่มเติมให้กับฉันเอง แต่มีคำแนะนำการตั้งค่าตามเงื่อนไขและมีคำแนะนำการย้ายตามเงื่อนไขอยู่ในโปรเซสเซอร์ P6 (Pentium Pro) หรือใหม่กว่า มีคำแนะนำมากมายที่ขึ้นอยู่กับการตั้งค่าสถานะหนึ่งใน EFLAGS


1
ฉันพบว่าการแตกกิ่งมักจะเล็กกว่า มีบางกรณีที่มันพอดีกับตัวเอง แต่cmovมี opcode ขนาด 2 ไบต์ ( 0F 4x +ModR/M) ดังนั้นจึงมีขนาดต่ำสุด 3 ไบต์ แต่แหล่งที่มาคือ r / m32 ดังนั้นคุณสามารถโหลดแบบมีเงื่อนไขใน 3 ไบต์ อื่น ๆ กว่ากิ่งจะเป็นประโยชน์ในกรณีมากกว่าsetcc cmovccยังคงพิจารณาชุดคำสั่งทั้งหมดไม่ใช่แค่พื้นฐาน 386 คำแนะนำ (แม้ว่าคำสั่ง SSE2 และ BMI / BMI2 มีขนาดใหญ่มากซึ่งไม่ค่อยมีประโยชน์ rorx eax, ecx, 326 ไบต์ยาวกว่า mov + ror ดีสำหรับการแสดงไม่ใช่กอล์ฟเว้นแต่ว่า POPCNT หรือ PDEP จะช่วยประหยัดเกาะได้มากมาย)
Peter Cordes

@PeterCordes setccขอบคุณฉันได้เพิ่ม
qwr

1

บันทึกเป็น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ไบต์ของ
Peter Cordes

นอกจากนี้คุณสามารถกำจัดcmpโดยการทำsub $'A'-10, %al; jae .was_alpha; add $('A'-10)-'0'. (ฉันคิดว่าฉันมีเหตุผลที่ถูกต้อง) โปรดทราบว่า'A'-10 > '9'ไม่มีความกำกวม การลบการแก้ไขสำหรับตัวอักษรจะตัดทศนิยมหลัก ดังนั้นนี่จึงปลอดภัยถ้าเราสมมติว่าอินพุตของเรานั้นเป็นเลขฐานสิบหกที่ถูกต้องเช่นเดียวกับที่คุณทำ
Peter Cordes

0

คุณสามารถดึงข้อมูลวัตถุตามลำดับจากสแต็กได้โดยตั้งค่า 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
Peter Cordes

มันมีประโยชน์มากขึ้นหลังจาก "lea esi, [esp + ขนาดของที่อยู่ ret]" ซึ่งจะห้ามการใช้ป๊อปเว้นแต่คุณจะมีการลงทะเบียนอะไหล่
peter ferrie

โอ้ฟังก์ชั่นเกิดขึ้น? ค่อนข้างหายากที่คุณต้องการ args มากกว่าที่จะมีการลงทะเบียนหรือว่าคุณต้องการให้ผู้โทรทิ้งไว้ในหน่วยความจำแทนที่จะส่งพวกเขาทั้งหมดในการลงทะเบียน (ฉันมีคำตอบที่เสร็จสิ้นแล้วครึ่งหนึ่งเกี่ยวกับการใช้การประชุมที่กำหนดเองในกรณีที่หนึ่งในการประชุมการโทรลงทะเบียนมาตรฐานไม่เหมาะอย่างสมบูรณ์)
Peter Cordes

cdecl แทนที่จะเป็น fastcall จะปล่อยพารามิเตอร์ไว้ใน stack และมันง่ายที่จะมีพารามิเตอร์จำนวนมาก ดูตัวอย่างของ github.com/peterferrie/tinycrypt
เตอร์ ferrie

0

สำหรับ codegolf และ ASM: ใช้คำแนะนำใช้เฉพาะการลงทะเบียนกดป๊อปอัปลดหน่วยความจำลงทะเบียนหรือหน่วยความจำทันที


0

ในการคัดลอกการลงทะเบียน 64- บิตให้ใช้push rcx; pop rdxแทนที่จะเป็น mov3
ขนาดตัวถูกดำเนินการเริ่มต้นของ 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 และไม่เกี่ยวกับการดูแลรักษาชุดเก่า แต่ดัน / ป๊อปไม่ได้ช่วยให้คุณแลกเปลี่ยนได้จริง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.