Der Compiler speichert diese in mehreren Registern und verwendet bei Bedarf mehrere Anweisungen, um diese Werte zu rechnen. Die meisten ISAs verfügen über eine Add-with-Carry-Anweisung wie x86,adc
die es ziemlich effizient macht, Ganzzahlen-Add / Sub mit erweiterter Genauigkeit auszuführen .
Zum Beispiel gegeben
fn main() {
let a = 42u128;
let b = a + 1337;
}
Der Compiler generiert Folgendes, wenn er ohne Optimierung für x86-64 kompiliert:
(Kommentare von @PeterCordes hinzugefügt)
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
wo Sie sehen können, dass der Wert 42
in rax
und gespeichert ist rcx
.
(Anmerkung des Herausgebers: x86-64 C-Aufrufkonventionen geben in RDX: RAX 128-Bit-Ganzzahlen zurück. Dies main
gibt jedoch überhaupt keinen Wert zurück. Das redundante Kopieren erfolgt ausschließlich durch Deaktivieren der Optimierung, und Rust prüft tatsächlich, ob das Debug überläuft Modus.)
Zum Vergleich hier der ASM für Rust 64-Bit-Ganzzahlen auf x86-64, bei dem kein Add-with-Carry erforderlich ist, sondern nur ein einzelnes Register oder ein Stack-Slot für jeden Wert.
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
Das Setb / Test ist immer noch völlig redundant: jc
(Sprung, wenn CF = 1) würde gut funktionieren.
Wenn die Optimierung aktiviert ist, prüft der Rust-Compiler nicht, ob ein Überlauf +
vorliegt .wrapping_add()
.