Bug 113565 - __builtin_clz(0) is undefined behavior, but not detected in constant expressions
Summary: __builtin_clz(0) is undefined behavior, but not detected in constant expressions
Status: NEW
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 14.0
: P3 enhancement
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: diagnostic
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2024-01-23 19:19 UTC by Jan Schultke
Modified: 2024-01-24 10:16 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2024-01-23 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jan Schultke 2024-01-23 19:19:49 UTC
Code to reproduce
=================

static_assert(__builtin_clz(0));


Actual output (https://godbolt.org/z/v6v5nGxv8)
===============================================

None.


Expected output (Clang)
=======================

<source>:1:15: error: static assertion expression is not an integral constant expression
    1 | static_assert(__builtin_clz(0));
      |               ^~~~~~~~~~~~~~~~
Comment 1 Drea Pinski 2024-01-23 19:32:20 UTC
This should fix it (but I am have not tested it either and I am not 100% sure we want/need this):

```
[apinski@xeond2 gcc]$ git diff fold-const-call.cc
diff --git a/gcc/fold-const-call.cc b/gcc/fold-const-call.cc
index 47bf8d64391..c9fa51e35ee 100644
--- a/gcc/fold-const-call.cc
+++ b/gcc/fold-const-call.cc
@@ -1031,7 +1031,7 @@ fold_const_call_ss (wide_int *result, combined_fn fn, const wide_int_ref &arg,
          tmp = TYPE_PRECISION (arg_type);
        else if (!CLZ_DEFINED_VALUE_AT_ZERO (SCALAR_INT_TYPE_MODE (arg_type),
                                             tmp))
-         tmp = TYPE_PRECISION (arg_type);
+         return false;
        *result = wi::shwi (tmp, precision);
        return true;
       }
@@ -1046,7 +1046,7 @@ fold_const_call_ss (wide_int *result, combined_fn fn, const wide_int_ref &arg,
          tmp = TYPE_PRECISION (arg_type);
        else if (!CTZ_DEFINED_VALUE_AT_ZERO (SCALAR_INT_TYPE_MODE (arg_type),
                                             tmp))
-         tmp = TYPE_PRECISION (arg_type);
+         return false;
        *result = wi::shwi (tmp, precision);
        return true;
       }

```

Confirmed.
Comment 2 Drea Pinski 2024-01-23 19:33:39 UTC
Note starting in GCC 14, it is better to use __builtin_clzg with the 2 arguments anyways so you can control exactly what value you want/need for 0.
Comment 3 Jonathan Wakely 2024-01-24 09:54:26 UTC
I'm not sure it's reasonable to expect an error here. The C++ standard says nothing about whether __builtin_clz is a constant expression, obviously.

So I'm changing this to severity=enhancement.
Comment 4 Jan Schultke 2024-01-24 10:04:16 UTC
I would expect an error here because things that are undefined behavior are generally supposed to fail in constant expressions. I don't see a good reason why builtins should be exempt from that rule.

The lack of diagnostic has cost me a few minutes of debugging yesterday. I had a static_assert:

> static_assert(my_function(0u) == 32);

This succeeded at compile time, but my_function(0) would return 0 at run-time as a result of passing through to __builtin_clz. UBSan may have caught it, but regardless, it's not sane to have different results inside/outside constant expressions like that.
Comment 5 Jan Schultke 2024-01-24 10:16:17 UTC
You can reproduce this as follows:

> static_assert(__builtin_clz(0u) == 32);
> 
> unsigned x = 0;
> 
> int main() {
>     return __builtin_clz(x);
> }


For base x86_64, GCC emits: (https://godbolt.org/z/nqzYrTWd1)

> main:
>         bsr     eax, DWORD PTR x[rip]
>         xor     eax, 31
>         ret
> x:
>         .zero   4

Even though __builtin_clz(0u) == 32 passes, this program returns 63. This is obviously not in the interest of the developer, regardless of what the standard mandates.