จำนวนเต็ม 128 บิตของ Rust "i128" ทำงานอย่างไรบนระบบ 64 บิต


128

Rust มีจำนวนเต็ม 128 บิตซึ่งแสดงด้วยชนิดข้อมูลi128(และu128สำหรับ ints ที่ไม่ได้ลงชื่อ):

let a: i128 = 170141183460469231731687303715884105727;

Rust ทำให้i128ค่าเหล่านี้ทำงานบนระบบ 64 บิตได้อย่างไร เช่นมันคำนวณอย่างไรกับสิ่งเหล่านี้?

เนื่องจากเท่าที่ฉันทราบค่านี้ไม่สามารถใส่ลงในรีจิสเตอร์ x86-64 ซีพียูได้คอมไพเลอร์ใช้ 2 รีจิสเตอร์สำหรับi128ค่าเดียวหรือไม่ หรือพวกเขาใช้โครงสร้างจำนวนเต็มใหญ่แทนแทน?



54
จำนวนเต็มสองหลักทำงานอย่างไรเมื่อคุณมีเพียง 10 นิ้ว?
Jörg W Mittag

27
@JorgWMittag: อา - อุบาย "เลขสองหลักสิบนิ้ว" แบบเก่า หึหึ- คิดว่าคุณหลอกฉันด้วยคนเก่าได้ใช่มั้ย? เพื่อนของฉันอย่างที่นักเรียนชั้นประถมศึกษาปีที่สองสามารถบอกคุณได้ - นั่นคือสิ่งที่นิ้วเท้ามีไว้! ( ด้วยคำขอโทษอย่างสุดซึ้งต่อ Peter Sellers ... และ Lady Lytton :-)
Bob Jarvis - คืนสถานะ Monica

1
FWIW เครื่อง x86 ส่วนใหญ่มีการลงทะเบียนพิเศษ 128 บิตหรือใหญ่กว่าสำหรับการทำงานของ SIMD ดูen.wikipedia.org/wiki/Streaming_SIMD_Extensions Edit: ฉันพลาดความคิดเห็นของ @ eckes
Ryan1729

4
@ JörgWMittag Nah นักวิทยาศาสตร์คอมพิวเตอร์นับเป็นเลขฐานสองโดยการลดหรือขยายนิ้วแต่ละนิ้ว ตอนนี้ 132 คุณกำลังจะกลับบ้าน ;-D
Marco13

คำตอบ:


141

ทั้งหมดของสนิมประเภทจำนวนเต็มจะถูกรวบรวมเพื่อจำนวนเต็ม LLVM เครื่องนามธรรม LLVM อนุญาตให้มีจำนวนเต็มที่มีความกว้างบิตตั้งแต่ 1 ถึง 2 ^ 23 - 1 * โดยทั่วไปคำสั่ง LLVM จะทำงานกับจำนวนเต็มทุกขนาด

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

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

คอมไพลเลอร์ใช้ 2 รีจิสเตอร์สำหรับi128ค่าเดียวหรือไม่? หรือพวกเขาใช้โครงสร้างจำนวนเต็มใหญ่เพื่อแสดงถึงพวกเขา?

ที่ระดับ LLVM IR คำตอบคือ: i128พอดีในการลงทะเบียนเพียงครั้งเดียวเช่นเดียวกับที่อื่น ๆ ทุกชนิดเดียวที่มีมูลค่า ในทางกลับกันเมื่อแปลเป็นรหัสเครื่องแล้วจะไม่มีความแตกต่างระหว่างทั้งสองเนื่องจากโครงสร้างอาจถูกย่อยสลายเป็นรีจิสเตอร์เหมือนกับจำนวนเต็ม อย่างไรก็ตามเมื่อทำเลขคณิตมันเป็นการเดิมพันที่ค่อนข้างปลอดภัยที่ LLVM จะโหลดทุกอย่างเป็นสองรีจิสเตอร์


* อย่างไรก็ตามแบ็กเอนด์ LLVM ทั้งหมดไม่ได้ถูกสร้างขึ้นเท่ากัน คำตอบนี้เกี่ยวข้องกับ x86-64 ฉันเข้าใจว่าการรองรับแบ็กเอนด์สำหรับขนาดที่ใหญ่กว่า 128 และไม่ใช่พาวเวอร์ของสองตัวนั้นไม่แน่นอน (ซึ่งบางส่วนอาจอธิบายได้ว่าทำไม Rust จึงแสดงจำนวนเต็ม 8-, 16-, 32-, 64- และ 128 บิตเท่านั้น) อ้างอิงจาก est31 บน Reddit Rustc ใช้จำนวนเต็ม 128 บิตในซอฟต์แวร์เมื่อกำหนดเป้าหมายแบ็กเอนด์ที่ไม่รองรับโดยกำเนิด


1
ฉันสงสัยว่าทำไมมันถึงเป็น 2 ^ 23 แทนที่จะเป็น 2 ^ 32 ทั่วไป (พูดอย่างกว้าง ๆ ในแง่ของความถี่ที่ตัวเลขเหล่านั้นปรากฏไม่ใช่ในแง่ของความกว้างบิตสูงสุดของจำนวนเต็มที่สนับสนุนโดยแบ็กเอนด์ของคอมไพเลอร์ ... )
กองทุน คดีของโมนิกา

26
@NicHartley คลาสพื้นฐานของ LLVM บางส่วนมีเขตข้อมูลที่คลาสย่อยสามารถจัดเก็บข้อมูลได้ สำหรับTypeคลาสนี้หมายความว่ามี 8 บิตในการจัดเก็บประเภทของประเภท (ฟังก์ชันบล็อกจำนวนเต็ม ... ) และ 24 บิตสำหรับข้อมูลคลาสย่อย IntegerTypeระดับแล้วใช้เหล่านั้น 24 บิตในการจัดเก็บขนาดที่ช่วยให้กรณีไปอย่างเรียบร้อยพอดีใน 32 บิต!
Todd Sewell

56

คอมไพลเลอร์จะจัดเก็บสิ่งเหล่านี้ไว้ในรีจิสเตอร์หลายรายการและใช้คำสั่งหลายคำสั่งเพื่อคำนวณค่าเหล่านั้นหากจำเป็น ISAs ส่วนใหญ่มีคำสั่งเสริมพร้อมพกพาเช่นx86adcซึ่งทำให้การเพิ่ม / ย่อยจำนวนเต็มความแม่นยำสูงมีประสิทธิภาพพอสมควร

ตัวอย่างเช่นให้

fn main() {
    let a = 42u128;
    let b = a + 1337;
}

คอมไพเลอร์สร้างสิ่งต่อไปนี้เมื่อคอมไพล์สำหรับ x86-64 โดยไม่มีการปรับให้เหมาะสม:
(ความคิดเห็นที่เพิ่มโดย @PeterCordes)

playground::main:
    sub rsp, 56
    mov qword ptr [rsp + 32], 0
    mov qword ptr [rsp + 24], 42         # store 128-bit 0:42 on the stack
                                         # little-endian = low half at lower address

    mov rax, qword ptr [rsp + 24]
    mov rcx, qword ptr [rsp + 32]        # reload it to registers

    add rax, 1337                        # add 1337 to the low half
    adc rcx, 0                           # propagate carry to the high half. 1337u128 >> 64 = 0

    setb    dl                           # save carry-out (setb is an alias for setc)
    mov rsi, rax
    test    dl, 1                        # check carry-out (to detect overflow)
    mov qword ptr [rsp + 16], rax        # store the low half result
    mov qword ptr [rsp + 8], rsi         # store another copy of the low half
    mov qword ptr [rsp], rcx             # store the high half
                             # These are temporary copies of the halves; probably the high half at lower address isn't intentional
    jne .LBB8_2                       # jump if 128-bit add overflowed (to another not-shown block of code after the ret, I think)

    mov rax, qword ptr [rsp + 16]
    mov qword ptr [rsp + 40], rax     # copy low half to RSP+40
    mov rcx, qword ptr [rsp]
    mov qword ptr [rsp + 48], rcx     # copy high half to RSP+48
                  # This is the actual b, in normal little-endian order, forming a u128 at RSP+40
    add rsp, 56
    ret                               # with retval in EAX/RAX = low half result

ซึ่งคุณจะเห็นว่าค่า42ถูกเก็บไว้ในraxและrcxและ

(หมายเหตุของบรรณาธิการ: ข้อตกลงการโทร x86-64 C ส่งคืนจำนวนเต็ม 128 บิตใน RDX: RAX แต่นี่ mainไม่ส่งคืนค่าเลยการคัดลอกซ้ำซ้อนทั้งหมดมาจากการปิดใช้งานการเพิ่มประสิทธิภาพเท่านั้นและ Rust จะตรวจสอบการล้นในการดีบัก โหมด.)

สำหรับการเปรียบเทียบนี่คือ asm สำหรับ Rust 64-bit จำนวนเต็มบน x86-64 ซึ่งไม่จำเป็นต้องใช้ add-with-carry มีเพียง register เดียวหรือ stack-slot สำหรับแต่ละค่า

playground::main:
    sub rsp, 24
    mov qword ptr [rsp + 8], 42           # store
    mov rax, qword ptr [rsp + 8]          # reload
    add rax, 1337                         # add
    setb    cl
    test    cl, 1                         # check for carry-out (overflow)
    mov qword ptr [rsp], rax              # store the result
    jne .LBB8_2                           # branch on non-zero carry-out

    mov rax, qword ptr [rsp]              # reload the result
    mov qword ptr [rsp + 16], rax         # and copy it (to b)
    add rsp, 24
    ret

.LBB8_2:
    call panic function because of integer overflow

setb / test ยังคงซ้ำซ้อนโดยสิ้นเชิง: jc(กระโดดถ้า CF = 1) จะทำงานได้ดี

ด้วยการเพิ่มประสิทธิภาพการเปิดใช้คอมไพเลอร์สนิมไม่ได้ตรวจสอบสำหรับล้นเพื่อให้การทำงานเช่น+.wrapping_add()


4
@Anush No, rax / rsp / ... เป็นรีจิสเตอร์ 64 บิต หมายเลข 128 บิตแต่ละตัวจะถูกเก็บไว้ในตำแหน่งรีจิสเตอร์ / หน่วยความจำสองตำแหน่งซึ่งส่งผลให้มีการเพิ่ม 64 บิตสองรายการ
ManfP

5
@Anush: ไม่มันแค่ใช้คำสั่งมากมายเพราะคอมไพล์โดยปิดการเพิ่มประสิทธิภาพ คุณจะเห็นโค้ดที่ง่ายกว่ามาก (เช่นแค่ add / adc) หากคุณรวบรวมฟังก์ชันที่ใช้สองu128args และส่งคืนค่า (เช่นgodbolt.org/z/6JBza0 ) แทนที่จะปิดใช้งานการปรับให้เหมาะสมเพื่อหยุดคอมไพเลอร์ไม่ให้ทำ การขยายพันธุ์คงที่บนอาร์กิวเมนต์คงที่เวลาคอมไพล์
Peter Cordes

3
@ CAD97 Release mode ใช้การตัดเลขคณิต แต่ไม่ตรวจสอบการล้นและความตื่นตระหนกเหมือนโหมดดีบัก พฤติกรรมนี้ถูกกำหนดโดยRFC 560 ไม่ใช่ยูบีนะ
trentcl

3
@PeterCordes: โดยเฉพาะ Rust ภาษาระบุว่า overflow ไม่ได้ระบุไว้และ rustc (คอมไพเลอร์เพียงตัวเดียว) ระบุพฤติกรรมสองอย่างให้เลือก: Panic หรือ Wrap ตามหลักการแล้ว Panic จะถูกใช้โดยค่าเริ่มต้น ในทางปฏิบัติเนื่องจากการสร้างโค้ดที่ไม่เหมาะสมในโหมดรีลีสค่าเริ่มต้นคือ Wrap และเป้าหมายระยะยาวคือการย้ายไปที่ Panic เมื่อ (ถ้าเคย) การสร้างโค้ดนั้น "ดีพอ" สำหรับการใช้งานทั่วไป นอกจากนี้ประเภทอินทิกรัล Rust ทั้งหมดยังสนับสนุนการดำเนินการที่ตั้งชื่อเพื่อเลือกลักษณะการทำงาน: การตรวจสอบการห่อการทำให้อิ่มตัว ... ดังนั้นคุณสามารถแทนที่พฤติกรรมที่เลือกตามการดำเนินการ
Matthieu M.

1
@ MatthieuM: ใช่ฉันชอบการตัดกับการตรวจสอบเทียบกับการเพิ่ม / ย่อย / กะ / วิธีการใดก็ตามในประเภทดั้งเดิม ดีกว่าการห่อของ C ที่ไม่ได้ลงนาม UB เซ็นชื่อบังคับให้คุณเลือกตามนั้น อย่างไรก็ตาม ISAs บางตัวสามารถให้การสนับสนุน Panic ได้อย่างมีประสิทธิภาพเช่นธงติดหนึบที่คุณสามารถตรวจสอบได้หลังจากดำเนินการตามลำดับ (ไม่เหมือนกับ OF หรือ CF ของ x86 ที่เขียนทับด้วย 0 หรือ 1) เช่น ForwardCom ISA ที่เสนอของ Agner Fog ( agner.org/optimize/blog/read.php?i=421#478 ) แต่ยังคง จำกัด การเพิ่มประสิทธิภาพที่จะไม่ทำการคำนวณใด ๆ แหล่งสนิมไม่ได้ทำ : /
Peter Cordes

30

ใช่เช่นเดียวกับจำนวนเต็ม 64 บิตบนเครื่อง 32 บิตหรือจำนวนเต็ม 32 บิตบนเครื่อง 16 บิตหรือแม้แต่จำนวนเต็ม 16 และ 32 บิตบนเครื่อง 8 บิต (ยังใช้ได้กับไมโครคอนโทรลเลอร์! ) ใช่คุณเก็บหมายเลขไว้ในรีจิสเตอร์สองแห่งหรือตำแหน่งหน่วยความจำหรืออะไรก็ตาม (มันไม่สำคัญจริงๆ) การบวกและการลบเป็นเรื่องเล็กน้อยโดยใช้คำสั่งสองคำสั่งและใช้แฟล็กพกพา การคูณต้องใช้การคูณสามครั้งและการเพิ่มบางอย่าง (เป็นเรื่องปกติที่ชิป 64 บิตจะมีการคูณ 64x64-> 128 ที่ส่งออกไปยังสองรีจิสเตอร์) ส่วน ... ต้องการรูทีนย่อยและค่อนข้างช้า (ยกเว้นในบางกรณีที่การหารด้วยค่าคงที่สามารถเปลี่ยนเป็นการกะหรือคูณได้) แต่ก็ยังใช้งานได้ Bitwise และ / หรือ / x หรือต้องทำเพียงครึ่งบนและล่างแยกกัน การเปลี่ยนแปลงสามารถทำได้ด้วยการหมุนและการกำบัง และนั่นก็ครอบคลุมสิ่งต่างๆ


26

เพื่อให้ตัวอย่างที่ชัดเจนขึ้นบน x86_64 คอมไพล์ด้วย-Oแฟล็กฟังก์ชัน

pub fn leet(a : i128) -> i128 {
    a + 1337
}

รวบรวมถึง

example::leet:
  mov rdx, rsi
  mov rax, rdi
  add rax, 1337
  adc rdx, 0
  ret

(โพสต์เดิมของฉันมีu128มากกว่าที่i128คุณถามฟังก์ชันนี้รวบรวมรหัสเดียวกันไม่ว่าจะด้วยวิธีใดการสาธิตที่ดีที่การเพิ่มที่เซ็นชื่อและไม่ได้ลงนามจะเหมือนกันใน CPU สมัยใหม่)

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

พารามิเตอร์aของฟังก์ชันนี้ถูกส่งผ่านคู่ของรีจิสเตอร์ 64 บิต rsi: rdi ผลลัพธ์จะถูกส่งคืนในรีจิสเตอร์คู่อื่น rdx: rax aสองบรรทัดแรกของรหัสเริ่มต้นรวมกับ

บรรทัดที่สามเพิ่ม 1337 ในคำต่ำของอินพุต หากสิ่งนี้ล้นจะมี 1 ในแฟล็กพกพาของ CPU บรรทัดที่สี่จะเพิ่มศูนย์ให้กับคำสูงของอินพุต - บวก 1 หากมีการดำเนินการ

คุณสามารถคิดว่านี่เป็นเพียงการเพิ่มตัวเลขหนึ่งหลักให้เป็นตัวเลขสองหลัก

  a  b
+ 0  7
______
 

แต่อยู่ในฐาน 18,446,744,073,709,551,616 คุณยังคงต้องเพิ่ม "หลัก" ที่ต่ำที่สุดก่อนโดยอาจถือ 1 ไปยังคอลัมน์ถัดไปจากนั้นจึงเพิ่มตัวเลขถัดไปบวกค่าพกพา การลบมีความคล้ายคลึงกันมาก

การคูณต้องใช้ข้อมูลประจำตัว (2⁶⁴a + b) (2⁶⁴c + d) = 2¹²⁸ac + 2⁶⁴ (ad + bc) + bd โดยที่การคูณแต่ละครั้งจะส่งกลับครึ่งบนของผลคูณในทะเบียนเดียวและครึ่งล่างของผลคูณใน อื่น คำศัพท์เหล่านั้นบางคำจะu128หายไปเนื่องจากบิตที่อยู่เหนือ 128 ไม่พอดีกับ a และจะถูกทิ้งไป ถึงกระนั้นก็ต้องใช้คำแนะนำเครื่องหลายประการ กองยังดำเนินการหลายขั้นตอน สำหรับค่าที่ลงนามการคูณและการหารยังจำเป็นต้องแปลงสัญญาณของตัวถูกดำเนินการและผลลัพธ์ การดำเนินการเหล่านั้นไม่มีประสิทธิภาพมากนัก

ในสถาปัตยกรรมอื่นจะง่ายขึ้นหรือยากขึ้น RISC-V กำหนดส่วนขยายชุดคำสั่ง 128 บิตแม้ว่าความรู้ของฉันจะไม่มีใครนำมาใช้ในซิลิกอน หากไม่มีส่วนขยายนี้คู่มือสถาปัตยกรรม RISC-V จะแนะนำสาขาตามเงื่อนไข:addi t0, t1, +imm; blt t0, t1, overflow

SPARC มีรหัสควบคุมเหมือนกับแฟล็กควบคุมของ x86 แต่คุณต้องใช้คำสั่งพิเศษadd,ccเพื่อตั้งค่า ในทางกลับกัน MIPS ต้องการให้คุณตรวจสอบว่าผลรวมของจำนวนเต็มสองจำนวนที่ไม่ได้ลงนามนั้นน้อยกว่าหนึ่งในตัวถูกดำเนินการอย่างเคร่งครัดหรือไม่ ถ้าเป็นเช่นนั้นการเพิ่มจะล้นออกไป อย่างน้อยคุณก็สามารถตั้งค่าการลงทะเบียนอื่นให้เป็นมูลค่าของบิตพกพาได้โดยไม่ต้องมีสาขาตามเงื่อนไข


1
ย่อหน้าสุดท้าย: ในการตรวจสอบว่าตัวเลขที่ไม่ได้ลงชื่อสองตัวใดมีค่ามากกว่าโดยดูจากsubผลลัพธ์ที่มีค่าสูงคุณต้องมีn+1ผลลัพธ์ย่อยnบิตสำหรับอินพุตบิต กล่าวคือคุณต้องดูที่การดำเนินการไม่ใช่บิตเครื่องหมายของผลลัพธ์ที่มีความกว้างเท่ากัน นั่นเป็นเหตุผลที่ x86 เงื่อนไขสาขาที่ไม่ได้ลงนามขึ้นอยู่กับ CF (บิต 64 หรือ 32 ของผลลัพธ์เชิงตรรกะทั้งหมด) ไม่ใช่ SF (บิต 63 หรือ 31)
Peter Cordes

1
re: divmod: แนวทางของ AArch64 คือจัดเตรียมการหารและคำสั่งที่ทำจำนวนเต็มx - (a*b)คำนวณส่วนที่เหลือจากเงินปันผลผลหารและตัวหาร (ซึ่งมีประโยชน์แม้กระทั่งตัวหารคงที่โดยใช้ผกผันคูณสำหรับส่วนการหาร) ฉันไม่ได้อ่านเกี่ยวกับ ISAs ที่หลอมรวมคำแนะนำ div + mod ในการดำเนินการ divmod เดียว ที่เรียบร้อย
Peter Cordes

1
re: แฟล็ก: ใช่เอาต์พุตแฟล็กคือเอาต์พุตที่ 2 ที่ OoO exec + register-renaming ต้องจัดการอย่างใด ซีพียู x86 จัดการกับมันโดยเก็บบิตพิเศษไว้สองสามบิตพร้อมกับผลลัพธ์จำนวนเต็มที่ค่า FLAGS ขึ้นอยู่ดังนั้นอาจสร้าง ZF, SF และ PF ได้ทันทีเมื่อจำเป็น ฉันคิดว่ามีสิทธิบัตรของ Intel เกี่ยวกับเรื่องนี้ ดังนั้นจึงช่วยลดจำนวนเอาต์พุตที่ต้องติดตามแยกกลับเป็น 1 (ในซีพียู Intel uop ไม่สามารถเขียนทะเบียนจำนวนเต็มมากกว่า 1 รายการได้เช่นmul r642 uops โดยอันที่ 2 เขียน RDX high half)
Peter Cordes

1
แต่เพื่อความแม่นยำในการขยายที่มีประสิทธิภาพแฟล็กนั้นดีมาก ปัญหาหลักคือไม่มีการเปลี่ยนชื่อรีจิสเตอร์สำหรับการดำเนินการตามลำดับ superscalar แฟล็กเป็นอันตราย WAW (เขียนหลังเขียน) แน่นอนว่าคำแนะนำในการพกพาเป็น 3 อินพุตและนั่นก็เป็นปัญหาสำคัญในการติดตาม Intel ก่อน Broadwell ถอดรหัสadc, sbbและcmov2 UOPs แต่ละ (Haswell แนะนำ 3-input uops สำหรับ FMA Broadwell ขยายเป็นจำนวนเต็ม)
Peter Cordes

1
RISC ISAs ที่มีแฟล็กมักจะทำให้การตั้งค่าแฟล็กเป็นทางเลือกซึ่งควบคุมโดยบิตพิเศษ เช่น ARM และ SPARC เป็นแบบนี้ PowerPC ตามปกติทำให้ทุกอย่างซับซ้อนมากขึ้น: มีการลงทะเบียนรหัสเงื่อนไข 8 รายการ (รวมอยู่ในทะเบียน 32 บิตเดียวสำหรับบันทึก / เรียกคืน) ดังนั้นคุณสามารถเปรียบเทียบเป็น cc0 หรือเป็น cc7 หรืออะไรก็ได้ แล้วและหรือหรือรหัสเงื่อนไขเข้าด้วยกัน! คำแนะนำ Branch และ cmov สามารถเลือก CR register ที่จะอ่านได้ สิ่งนี้ทำให้คุณมีความสามารถในการมีหลายแฟล็กแทนโซ่ในเที่ยวบินพร้อมกันเช่น x86 ADCX / ADOX alanclements.org/power%20pc.html
Peter Cordes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.