-1

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)
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Chris_F
  • 4,991
  • 5
  • 33
  • 63
  • What is wrong with using `unsigned long long n;`? Does that fail `_rdrand64_step (unsigned __int64* val)`? – chux - Reinstate Monica Mar 14 '21 at 05:52
  • @chux-ReinstateMonica my worry was that since Intel defines the intrinsic with `__int64`, there may be a compiler (or future compiler) that defines the intrinsic with `unsigned long` making this code break if I use `unsigned long long`. – Chris_F Mar 14 '21 at 07:12

2 Answers2

1

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.)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Whatever the intrinsics are doing, the _rdrand_step and related functions ultimately derive from the library I wrote to first excercise the RdRand instruction on the first test silicon. The current version of that library uses stdint and is here: https://github.com/dj-on-github/rdrand_stdint . You may find the functions familiar and I find that they are less bother than the intrinsics. – David Johnston Nov 28 '21 at 01:17
  • @DavidJohnston: `int _rdrand64_step (unsigned __int64* val)` is an intrinsic for the [`rdrand` instruction](https://www.felixcloutier.com/x86/rdrand) with 64-bit operand-size. (The asm manual entry for it does list `_rdrand64_step` ). It should always expand to exactly one `rdrand` instruction, with the CF result as the return value and the output register as the by-reference operand. I assume the intrinsic name includes "step" to remind users that they need to check the bool-as-int return value in a do{}while() or similar. (Presumably that was your idea and they used names from your lib.) – Peter Cordes Nov 28 '21 at 03:00
  • Being intrinsics, the compiler can branch on the CF result directly instead of materializing it as an actual `int` in a GP-integer register the way your library does with inline-asm. Thanks for linking your library, though, useful for the retry functions. – Peter Cordes Nov 28 '21 at 03:04
0

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

David Johnston
  • 976
  • 1
  • 8
  • 10