Can't use uint64_t with rdrand as it expects unsigned long long, but uint64_t is defined as unsigned long

778 views Asked by At

I've run into the following annoyance when trying to use rdrand intrinsic.

With my current compiler unsigned long and unsigned long long are both 64-bits. However, uint64_t is defined as unsigned long while _rdrand64_step expects a pointer to unsigned long long.

On Intel's website the function is defined as int _rdrand64_step (unsigned __int64* val). How can I handle this in a way that will be portable?

 #include <immintrin.h>
 #include <stdint.h>

 uint64_t example_rdrand64()
 {
     uint64_t n;
     _rdrand64_step(&n);
     return n;
 }

clang 11.0 -march=ivybridge -O2 (https://godbolt.org/z/55sjsG):

error: no matching function for call to '_rdrand64_step'
note: candidate function not viable: no known conversion
from 'unsigned int *' to 'unsigned long long *' for 1st argument
_rdrand64_step(unsigned long long *__p)
2

There are 2 answers

3
Peter Cordes On BEST ANSWER

Use unsigned long long n; You can still return it as a uint64_t.

It works fine on the Godbolt compiler explorer with current versions of all 4 major x86 compilers (GCC, clang, ICC, MSVC). Note that _rdrand64_step will only ever work on x86-64 C++ implementations, so that limits the scope of portability concerns a lot.

All 4 mainstream x86 compilers define _rdrand64_step with a type compatible with unsigned long long, so in this case it's safe to just follow clang's headers.

Unfortunately (or not), gcc/clang's immintrin.h doesn't actually define a __int64 type to match Intel's intrinsics documentation, otherwise you could use that. ICC and MSVC do let you actually use unsigned __int64 n. (https://godbolt.org/z/v4xnc5)


immintrin.h being available at all implies a lot of other things about the compiler environment and type widths, and it's highly unlikely (but not impossible) that some future x86-64 C implementation would make unsigned long long anything other than a qword (uint64_t).

Although if they did, maybe they'd just map Intel's __int64 to a different type, since Intel's docs never use long or long long, just __int64, e.g. AVX2 _mm256_maskload_epi64(__int64 const* mem_addr, __m256i mask). (Or even __m128i* for the movq load intrinsic:
__m128i _mm_loadl_epi64 (__m128i const* mem_addr).
Much later, a more sane __m128i _mm_loadu_si64 (void const* mem_addr) was introduced (along with AVX512 intrinsics.)

But still, a C++ implementation with an unsigned long long that wasn't exactly 64 bits would probably break some intrinsics code, so it's not a problem you need to spend any time really worrying about. In this instance, if it were wider, that would still be fine. You'd just be returning the low 64 bits of it, where _rdrand64_step(&n); put the result. (Or you'd get a compile error if that C++ implementation had the intrinsic take unsigned long or however they define uint64_t instead of unsigned long long).

So there's zero chance of silent data corruption / truncation on any hypothetical future C++ implementation. ISO C++ guarantees that unsigned long long is at least a 64-bit type. (Actually specifies it by value-range, and being unsigned that its value bits are plain binary, but same difference.)

You don't need portability to a DeathStation 9000, just to any hypothetical future compiler that anyone might actually want to use, which pretty much implies that it would want to be compatible with existing Intel-intrinsics codebases, if it provides that style of intrinsics at all. (Rather than a redesign from scratch with different names and types, in which case you'd have to change 2 lines in this function to get it to work.)

0
David Johnston On

I'm the designer of the RNG behind RdRand and RdSeed. I use my own library, not intrinsics since they always give me problems. The library is here : https://github.com/dj-on-github/rdrand_stdint