Skip to content

Potential undefined behavior in udiv128by64to64default() due to __builtin_clzll(0) (related to GCC Bug 101175) #1448

@lyican11

Description

@lyican11

Affected file

cosmopolitan/libc/intrin/udivmodti4.c

Relevant code

forceinline du_int udiv128by64to64default(du_int u1, du_int u0, du_int v,
                                          du_int *r) {
  const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT;
  const du_int b = 1ULL << (n_udword_bits / 2);
  du_int un1, un0, vn1, vn0, q1, q0, un64, un21, un10, rhat;
  si_int s;

  s = __builtin_clzll(v);
  if (s > 0) {
    v = v << s;
    un64 = (u1 << s) | (u0 >> (n_udword_bits - s));
    un10 = u0 << s;
  } else {
    un64 = u1;
    un10 = u0;
  }

  // ... remainder of the long division algorithm ...
}

Problem description

This function computes a 128-bit ÷ 64-bit unsigned division using manual normalization.
However, the line:

s = __builtin_clzll(v);

can invoke undefined behavior (UB) when v == 0, because according to GCC documentation:

“If x is 0, the result of __builtin_clz(x) is undefined.”

If the caller ever passes v == 0, this function will:

  1. Call __builtin_clzll(0)undefined behavior,
  2. Proceed to divide by vn1 = v >> 32 or directly by v, also undefined (division by zero).

This pattern matches the root cause of GCC Bug 101175 – "builtin_clz generates wrong bsr instruction",
where __builtin_clz(0) caused miscompilation or runtime faults due to undefined instruction selection (LZCNT/BSR) on x86 targets. the function itself provides no internal guard,
which means the undefined behavior may still be reached through unexpected inputs or optimizations.


Recommended fix

Add an explicit zero check at the start of the function, returning early or handling it consistently with the intended API contract.

For example:

forceinline du_int udiv128by64to64default(du_int u1, du_int u0, du_int v,
                                          du_int *r) {
  if (v == 0) {
    // Handle division by zero according to project policy.
    // e.g., set remainder to 0 and return max value, or assert.
    if (r) *r = 0;
    return 0; // or appropriate sentinel
  }

  const unsigned n_udword_bits = sizeof(du_int) * CHAR_BIT;
  const du_int b = 1ULL << (n_udword_bits / 2);
  si_int s = __builtin_clzll(v);

  // normalization logic continues...
}

This eliminates the __builtin_clzll(0) undefined behavior and prevents further invalid divisions.


Why it matters

  • Calling __builtin_clzll(0) is undefined and may lead to wrong code generation on some architectures (as seen in GCC PR101175).
  • A single-line guard removes both clzll(0) and division-by-zero hazards.
  • The change has zero runtime cost when v != 0 (the common case).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions