GLSL Signed 64-Bit Integer Addition Overflow
This article examines the arithmetic overflow behavior of signed
64-bit integer (int64_t) addition within GLSL compute
kernels. While the formal OpenGL Shading Language (GLSL) specification
defines signed integer overflow as undefined, the underlying hardware
implementation and modern intermediate representations like SPIR-V
enforce two's complement modular wrap-around. Below, we break down the
distinction between the language specification, SPIR-V execution rules,
hardware reality, and best practices for writing predictable compute
kernels.
The GLSL Specification: Undefined Behavior
According to the official GLSL core specification (as well as the
GL_ARB_gpu_shader_int64 and
GL_EXT_shader_explicit_arithmetic_types_int64 extensions),
signed integer arithmetic overflow is strictly undefined.
Unlike unsigned integer types (uint64_t), which are
explicitly specified to wrap around using modulo \(2^{64}\), the language does not formally
guarantee what happens when a signed operation exceeds
INT64_MAX (\(2^{63}-1\))
or drops below INT64_MIN (\(-2^{63}\)). In pure theoretical GLSL, the
compiler is not obligated to generate code that wraps, clamps, or
produces consistent results when signed overflow occurs.
The SPIR-V and Vulkan Reality: Two's Complement Wrap-Around
Most modern GLSL compute kernels are compiled ahead-of-time or just-in-time into SPIR-V for execution in Vulkan or modern OpenGL environments. In the SPIR-V specification, the rules for signed integer addition are explicitly defined:
- Addition operations on integers map to the
OpIAddinstruction. - The SPIR-V specification explicitly dictates that signed integer
overflow for
OpIAddevaluates via standard two's complement wrap-around.
Because the translation from GLSL to SPIR-V converts a standard
+ operator on an int64_t into an
OpIAdd instruction, compute kernels running via Vulkan or
modern SPIR-V consumption pipelines will reliably exhibit two's
complement wrapping. An overflow past 9223372036854775807
wraps around to -9223372036854775808.
GPU Hardware Architecture
Modern GPU Arithmetic Logic Units (ALUs) handle signed and unsigned integer addition using the exact same binary circuitry. At the silicon level, there is no distinct "signed add" instruction versus an "unsigned add" instruction in the hardware instruction sets of vendors like NVIDIA, AMD, or Intel.
Both operations perform identical bitwise additions discard the carry bit past the 64th bit. Consequently, native hardware execution naturally results in two's complement wrap-around regardless of the theoretical undefined behavior specified in the GLSL language manual.
Risks and Compiler Optimizations
Although the physical hardware wraps around, relying on signed overflow in high-level GLSL source code carries subtle risks:
- Undefined Behavior Optimizations: While GPU shader
compilers historically perform fewer aggressive undefined-behavior-based
optimizations than CPU compilers (such as Clang or GCC), a GLSL compiler
assuming undefined behavior could theoretically eliminate overflow
checks or assume that
a + 1 > ais always true. - Portability Across Drivers: Pure OpenGL drivers that compile high-level GLSL text directly to internal assembly could, in rare cases, produce non-wrapping behavior if specialized emulation routines or specific instruction sets are invoked.
Recommended Practices
To ensure fully defined, portable, and predictable behavior in compute kernels:
Use Unsigned Arithmetic for Modular Operations: If intentional wrap-around behavior is required, cast the operands to
uint64_tprior to the addition and cast the result back toint64_t:int64_t safe_add(int64_t a, int64_t b) { return int64_t(uint64_t(a) + uint64_t(b)); }Target SPIR-V Directly: When working in Vulkan or using tools like
glslangValidator, the generatedOpIAddguarantees that two's complement wrapping is respected by the driver's backend compiler.