Multi-precision integer arithmetic (BigInt, cryptographic field arithmetic) cannot be implemented efficiently without carry/borrow propagation and double-word multiplication primitives. Without them, compilers are forced to synthesize 64-bit multi-precision arithmetic using 32-bit limbs to capture overflows manually, throwing away roughly half the hardware throughput.
Here is the completed API proposal incorporating multiplication and carrying/borrowing primitives:
# --- Checked / Raising Variants ---
def __add__(lhs: Int, rhs: Int) raises OverflowError -> Int
def __sub__(lhs: Int, rhs: Int) raises OverflowError -> Int
def __mul__(lhs: Int, rhs: Int) raises OverflowError -> Int
# --- Saturating Variants ---
def sat_add(lhs: Int, rhs: Int) -> Int
def sat_sub(lhs: Int, rhs: Int) -> Int
def sat_mul(lhs: Int, rhs: Int) -> Int
# --- Carrying / Borrowing Primitives (ADC / SBB) ---
def carrying_add(lhs: Int, rhs: Int, carry: Bool) -> (Int, Bool)
def borrowing_sub(lhs: Int, rhs: Int, borrow: Bool) -> (Int, Bool)
# --- Multiplication & Wide Accumulation ---
def overflowing_mul(lhs: Int, rhs: Int) -> (Int, Bool)
def mul_hi(lhs: Int, rhs: Int) -> Int
def widening_mul(lhs: Int, rhs: Int) -> (Int, Int) # (low, high)
def carrying_mul_add(
lhs: Int,
rhs: Int,
carry: Int,
high_carry: Int = 0
) -> (Int, Int) # (low, high)
Core Mechanics & Hardware Lowering
1. Carrying Addition & Subtraction (carrying_add, borrowing_sub)
- Hardware lowering: Direct mapping to
ADC/SBB(x86-64) orADCS/SBCS(AArch64). - Chaining: Essential for ripple-carry loops in multi-word additions:
var carry: Bool = False
for i in range(limb_count):
(result[i], carry) = carrying_add(a[i], b[i], carry)
- Typed Throws Variant: If
OverflowErrorcarries payload (e.g.,struct OverflowError: var partial: Int), raising variants avoid tuple unpacking overhead in checked execution paths. However, carrying loops generally run faster using explicit(Int, Bool)returns to prevent unwinding frame overhead across hot loops.
2. Full-Width & High Multiplication (widening_mul, mul_hi)
-
Standard single-word
Int * Intdiscards the top word. Multi-precision multiplication requires full N \times N \to 2N bit products. -
widening_mul(lhs, rhs)computes (lhs \times rhs) returning(low, high). -
Lowers to
MULX(x86 BMI2) orUMULH+ standardMUL(AArch64). -
mul_hi(lhs, rhs)extracts only the upper word without computing or returning the low bits if they are unneeded.
3. The BigInt Multiplier Primitive (carrying_mul_add)
- The fundamental inner loop of Comba/Schoolbook multi-limb multiplication computes:
- Because (2^{64} - 1) \times (2^{64} - 1) + (2^{64} - 1) + (2^{64} - 1) = 2^{128} - 1, the result of multiplying two words and adding two full-width carry words is guaranteed to fit within two 64-bit words with zero overflow.
- Providing
carrying_mul_addenables a 1:1 mapping to AMD64MULX/ADCX/ADOXdual-carry instructions without register spilling.
Scalar vs. SIMD Codegen Notes
| Feature | Scalar Int (Word Size) |
SIMD Vector (SIMD[type, size]) |
|---|---|---|
| Flags Register | Natural access to carry/overflow flags (CF, OF). |
Vectors have no flags register. Emulating carry requires explicit masks and shift logic. |
| Carrying Ops | Fast sequential chaining (ADC loop). |
Inefficient for ripple-carry. Multi-word vector BigInt typically uses redundant representations (e.g., 52-bit limbs in 64-bit lanes via AVX-512 IFMA). |
| Overflow Reporting | Raising/typed throws work cleanly. | Mask-based reporting (returning vector comparison masks) is preferred over branching/raising. |