Per fornire forse un esempio più chiaro, su x86_64, compilato con il -Oflag, la funzione
pub fn leet(a : i128) -> i128 {
a + 1337
}
compila a
example::leet:
mov rdx, rsi
mov rax, rdi
add rax, 1337
adc rdx, 0
ret
(Il mio post originale aveva u128piuttosto che il i128tuo chiesto. La funzione compila lo stesso codice in entrambi i casi, una buona dimostrazione che l'aggiunta firmata e non firmata sono le stesse su una CPU moderna.)
L'altro elenco ha prodotto un codice non ottimizzato. È sicuro passare in un debugger, perché si assicura che sia possibile posizionare un punto di interruzione ovunque e ispezionare lo stato di qualsiasi variabile in qualsiasi riga del programma. È più lento e più difficile da leggere. La versione ottimizzata è molto più vicina al codice che verrà effettivamente eseguito in produzione.
Il parametro adi questa funzione viene passato in una coppia di registri a 64 bit, rsi: rdi. Il risultato viene restituito in un'altra coppia di registri, rdx: rax. Le prime due righe di codice inizializzano la somma in a.
La terza riga aggiunge 1337 alla parola bassa dell'input. Se questo trabocca, porta l'1 nel flag carry della CPU. La quarta riga aggiunge zero alla parola più alta dell'input, più 1 se viene trasportata.
Puoi pensare a questo come una semplice aggiunta di un numero di una cifra a un numero di due cifre
a b
+ 0 7
______
ma in base 18.446.744.073.709.551.616. Stai ancora aggiungendo prima la "cifra" più bassa, possibilmente portando un 1 nella colonna successiva, quindi aggiungendo la cifra successiva più il carry. La sottrazione è molto simile.
La moltiplicazione deve utilizzare l'identità (2⁶⁴a + b) (2⁶⁴c + d) = 2¹²⁸ac + 2⁶⁴ (ad + bc) + bd, dove ciascuna di queste moltiplicazioni restituisce la metà superiore del prodotto in un registro e la metà inferiore del prodotto in un altro. Alcuni di questi termini verranno eliminati, poiché i bit sopra il 128 ° non rientrano in a u128e vengono scartati. Anche così, questo richiede una serie di istruzioni della macchina. Anche la divisione compie diversi passaggi. Per un valore con segno, la moltiplicazione e la divisione dovrebbero inoltre convertire i segni degli operandi e il risultato. Tali operazioni non sono affatto molto efficienti.
Su altre architetture, diventa più facile o più difficile. RISC-V definisce un'estensione del set di istruzioni a 128 bit, sebbene per quanto ne sappia nessuno lo ha implementato in silicio. Senza questa estensione, il manuale di architettura RISC-V raccomanda un ramo condizionale:addi t0, t1, +imm; blt t0, t1, overflow
SPARC ha codici di controllo come i flag di controllo di x86, ma è necessario utilizzare un'istruzione speciale add,ccper impostarli. MIPS, d'altra parte, richiede di verificare se la somma di due numeri interi senza segno è strettamente inferiore a uno degli operandi. In tal caso, l'aggiunta è traboccata. Almeno sei in grado di impostare un altro registro sul valore del bit di riporto senza un ramo condizionale.