2

Given a helper method that does some bit manipulation and which is used sometimes at runtime and sometimes as a constexpr argument:

    /// <summary>Counts the number of leading zero bits in a value</summary>
    /// <param name="value">Value in which the leading zero bits will be couned</param>
    /// <returns>The number of leading zero bits in the value</returns>
    /// <remarks>
    ///   The result is undefined if the input value is 0
    /// </remarks>
    public: static inline constexpr unsigned char CountLeadingZeroBits(std::uint32_t value) {
    #if defined(_MSC_VER)
      return __lzcnt(value);
    #elif defined(__clang__) || (defined(__GNUC__) || defined(__GNUG__))
      return static_cast<unsigned char>(__builtin_clz(value));
    #else
      // https://www.chessprogramming.org/BitScan#Bitscan_reverse
      // https://stackoverflow.com/questions/2589096/
      const unsigned char deBruijnBitPosition[32] = {
        31, 22, 30, 21, 18, 10, 29, 2, 20, 17, 15, 13, 9, 6, 28, 1,
        23, 19, 11, 3, 16, 14, 7, 24, 12, 4, 8, 25, 5, 26, 27, 0
      };

      value |= value >> 1;
      value |= value >> 2;
      value |= value >> 4;
      value |= value >> 8;
      value |= value >> 16;

      return deBruijnBitPosition[(value * 0x07C4ACDDU) >> 27];
    #endif
    }

I am trying to optimize this method via compiler built-ins / intrinsics (already shown in the above snippet).

Whether intrinsics are usable in a constexpr setting is, of course, implementation-dependent. Of the compilers/envs I'm targeting, GCC 9.2 and clang 9.0 allow __builtin_clz() in constexpr, MSVC 14.1 (VS2017) does not.

Is there a way my function's implementation could detect whether it is being used in a constexpr setting (then doing manual bit manipulation) or at runtime (then using the compiler intrinsic)?

i.e.

    public: static inline constexpr unsigned char CountLeadingZeroBits(std::uint32_t value) {
    #if defined(_MSC_VER) && (!__method_used_as_constexpr)
      return __lzcnt(value);
    ...
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Cygon
  • 9,444
  • 8
  • 42
  • 50
  • GNU C has `__builtin_constant_p()` to test whether a value is actually a compile-time constant after constant-propagation optimizations - you might use that to select between inline asm or pure C. But with intrinsics you don't need that, and that's a slightly separate problem from constexpr. You might be out of luck with MSVC unless it has something similar. – Peter Cordes Mar 13 '20 at 00:18
  • Note that `__lzcnt` is an intrinsic for BMI1 `lzcnt`, and will execute differently on CPUs without BMI1 (as `rep bsr` = `bsr` which gives you a bit-index = 31-lzcnt). If you care about older CPUs, you might want to use an MSVC intrinsic for 31-BSR. Or if not, then you might want the portable Intel intrinsic `_lzcnt_u32()` everywhere including on GNU C. (Although GCC will use that for clz if you compile with `-mbmi` or `-march=haswell` or whatever.) Note that BSR is slower thatn LZCNT on AMD CPUs, as well as needing an xor with 31, and leaves the destination unmodified for input=0. – Peter Cordes Mar 13 '20 at 00:23
  • 1
    Can you work around it on MSVC using [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)? Hmm no, it has a `.count()` which lets a C++ library portably expose an optimized popcnt, but it doesn't have find first / find last. And you can't expect `std::vector` => std::find to work in constexpr. It's ridiculous that ISO C++ still doesn't portably expose things many CPUs can do efficiently. – Peter Cordes Mar 13 '20 at 00:25
  • 3
    Does this answer your question? [std::is\_constant\_evaluated behavior](https://stackoverflow.com/questions/54251530/stdis-constant-evaluated-behavior) (There is no way to emulate it before C++20, barring some compiler extensions; and we have [`std::countl_one`](https://en.cppreference.com/w/cpp/numeric/countl_one) in C++20 anyway) – L. F. Mar 13 '20 at 00:44
  • @L.F. Yes, that's the functionality I'm looking for, though I was hoping that said compiler extensions might exist in MSVC 14.1. So far, all I have found is an abandoned UserVoice(-ish) idea: https://developercommunity.visualstudio.com/idea/434712/make-intrinsic-functions-supported-in-a-constexpr.html and a discussion in an isocpp mailing list: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/NErPRYCz3ZU with no solutions. – Cygon Mar 13 '20 at 12:51

0 Answers0