How GLSL umulExtended Computes 64-Bit Products

The GLSL built-in function umulExtended calculates the full 64-bit result of multiplying two 32-bit unsigned integers without truncation. While standard GLSL multiplication drops the upper bits to return a 32-bit value, umulExtended retains the entire mathematical product by splitting it across two separate 32-bit output variables. This article covers the mathematical principles, function signature, and hardware-level execution that make this extended-precision operation possible.

Function Signature and Parameters

The umulExtended function is defined in the OpenGL Shading Language as:

void umulExtended(genUType x, genUType y, out genUType msb, out genUType lsb);

The parameters operate as follows:

  • x: The first 32-bit unsigned integer factor (scalar or vector).
  • y: The second 32-bit unsigned integer factor (scalar or vector).
  • msb: An output variable that receives the most significant 32 bits (bits 32 through 63) of the product.
  • lsb: An output variable that receives the least significant 32 bits (bits 0 through 31) of the product.

Because GLSL types are vectorized, genUType can be a scalar uint or any vector type from uvec2 to uvec4. When vectors are passed, the operation executes component-wise.

Mathematical Representation

Multiplying two maximum 32-bit unsigned integers (\(2^{32} - 1\)) yields:

\[(2^{32} - 1) \times (2^{32} - 1) = 2^{64} - 2^{33} + 1\]

This product requires up to 64 bits of storage. Standard multiplication in GLSL performs modular arithmetic (\(x \times y \pmod{2^{32}}\)), which discards any overflow beyond bit 31.

umulExtended preserves the entire 64-bit product by representing the true value through the following identity:

\[\text{Product} = (\text{msb} \times 2^{32}) + \text{lsb}\]

The lsb contains the standard lower 32 bits, while msb captures the overflow values shifted right by 32 bits:

  • \(\text{lsb} = (x \times y) \ \& \ \text{0xFFFFFFFF}\)
  • \(\text{msb} = (x \times y) \gg 32\)

Hardware Execution

Rather than emulating a 64-bit operation through multiple arithmetic shifts and additions, modern GPU Arithmetic Logic Units (ALUs) natively support 32-bit wide multiplication.

At the hardware instruction set architecture (ISA) level:

  • Many GPU architectures feature instructions that write both the low and high 32-bit halves of a product into register pairs in a single or dual clock cycle.
  • On architectures like NVIDIA (PTX), this maps to mul.lo.u32 (low 32 bits) and mul.hi.u32 (high 32 bits), or mul.wide.u32, which directly outputs into a 64-bit register pair.
  • AMD architectures use equivalent vector ALU instructions such as V_MUL_LO_U32 and V_MUL_HI_U32.

The GLSL compiler recognizes umulExtended and directly emits these specialized ISA instructions, avoiding software-emulated multi-precision math.

GLSL Usage Example

#version 450 core

void main() {
    uint a = 3000000000u;
    uint b = 2000000000u;
    
    uint highBits;
    uint lowBits;
    
    // Compute full 64-bit product: 6,000,000,000,000,000,000
    umulExtended(a, b, highBits, lowBits);
    
    // highBits contains bits 32-63
    // lowBits contains bits 0-31
}

By storing the result across two registers, umulExtended provides the necessary building block for implementing arbitrary-precision arithmetic, software-emulated 64-bit integer logic, and high-precision hashing algorithms directly in shader pipelines.