GLSL Binary Additive Operations Evaluation Order

In the OpenGL Shading Language (GLSL), the evaluation order of operand expressions in binary additive operations (+ and -) is completely unspecified. Although the GLSL specification strictly dictates left-to-right associativity to define how operations are grouped mathematically, it does not guarantee whether the left-hand or right-hand operand expression executes first. Consequently, relying on a specific execution sequence across additive operands—particularly when expressions involve side effects—results in non-portable and unpredictable behavior across different GPU drivers and hardware architectures.

Associativity vs. Evaluation Order

A common point of confusion in shader programming is the distinction between operator associativity and operand evaluation order:

  • Associativity defines the syntactic grouping of operations. Binary addition and subtraction in GLSL are left-associative. This means an expression such as a + b + c is parsed and grouped as (a + b) + c.
  • Evaluation Order defines the temporal sequence in which the subexpressions themselves are computed. In an expression like exprA() + exprB(), associativity dictates that the result of exprA() is added to exprB(), but it does not mandate whether exprA() or exprB() executes first.

The GLSL Specification Stance

According to the official GLSL specification (specifically within the section detailing expressions and operators):

  1. Unspecified Sequence: The order of evaluation of expressions is not fully defined. Compilers are free to evaluate the left operand before the right operand, the right operand before the left operand, or both concurrently if hardware resources allow.
  2. Compiler Reordering and Optimization: Shading language compilers possess significant leeway to reorder, fold, or vectorize arithmetic expressions, provided the mathematical outcome adheres to IEEE floating-point and GLSL precision qualifiers. Compilers can rearrange expressions algebraically unless strict floating-point rules or qualifiers (such as invariant or precise) restrict them.
  3. No Short-Circuiting: Unlike logical operators such as && and ||, binary additive operators do not introduce sequence points or short-circuit behavior. Both operands are evaluated without any sequencing guarantees between them.

Side Effects and Undefined Behavior

Because operand evaluation order is unspecified, expressions that produce side effects can lead to critical bugs. A side effect occurs when an expression modifies a variable, such as through post-increment operations (x++), assignment within subexpressions, or calls to functions with out or inout parameters that modify shared or global state.

Consider the following example:

int x = 1;
int result = (x += 2) + (x * 3);

Depending on the GPU vendor's compiler:

  • If the left operand (x += 2) is evaluated first, x becomes 3, and the right operand evaluates to 3 * 3 = 9, resulting in 3 + 9 = 12.
  • If the right operand (x * 3) is evaluated first, it computes 1 * 3 = 3, then x becomes 3, resulting in 3 + 3 = 6.

Both outputs are valid according to the GLSL specification, making the code non-deterministic.

Writing Robust GLSL Expressions

To prevent driver-specific compilation differences and hard-to-debug graphical artifacts:

  • Isolate Side Effects: Never modify variables within the same additive expression where they are also read.
  • Separate Expressions into Explicit Statements: When using functions with out or inout parameters, invoke them as standalone statements on separate lines before combining their results with additive operators.
  • Do Not Rely on Subexpression Ordering: Treat all operand expressions in arithmetic operations as if they occur simultaneously or in arbitrary order.