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)
Use
unsigned long long n;
You can still return it as auint64_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 withunsigned 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 useunsigned __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 makeunsigned 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 uselong
orlong long
, just__int64
, e.g. AVX2_mm256_maskload_epi64(__int64 const* mem_addr, __m256i mask)
. (Or even__m128i*
for themovq
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 takeunsigned long
or however they defineuint64_t
instead ofunsigned 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.)