1

I'm working on a C++ project in Visual Studio 2008 IDE, where I need to use Intel's new RDRAND instruction. I did a quick search, and MSDN recommends using _rdrand64_step intrinsic defined in immintrin.h, which I do not have in VS 2008.

In a 32-bit compiled code I can get away with using asm keyword as such:

    __asm
    {
        xor eax, eax

        ;RDRAND instruction = Set random value into EAX.
        ;Will set overflow [C] flag if success
        _emit 0x0F
        _emit 0xC7
        _emit 0xF0
    }

But on x64 asm is not supported.

Can you suggest how can I compile my project for 64-bit with the RDRAND instruction?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • With asm disabled, there is no longer any way to use new ops with old compilers. You *could* compile one standalone object file separately though and then link it though ... – o11c Jun 27 '16 at 00:51
  • Can someone with the later version of VS (2012 and up) copy and paste the intrinsic definition for `_rdrand64_step`? Or does it come with a separate binary file too? – c00000fd Jun 27 '16 at 00:55
  • 2
    @c00000fd It's an intrinsic. There is no definition. That's what intrinsic means. – user253751 Jun 27 '16 at 00:58
  • @immibis: What is it compiled from? Is it some internal definition within a particular compiler, that in my case VS2008 doesn't have? – c00000fd Jun 27 '16 at 01:00
  • @c00000fd The compiler treats it specially, so yes it's internal to the compiler. Imagine the compiler has some code like `if(functionName == "_rdrand64_step") emitRDRANDInstruction(); else {/* do normal function call stuff */}` – user253751 Jun 27 '16 at 01:02
  • @immibis: Oh, OK. Thanks. In that case I'm out of the water with this.... – c00000fd Jun 27 '16 at 01:03

3 Answers3

5

You either need to upgrade your compiler to one that does support the _rdrand64_step intrinsic (supported since Visual Studio 2012), or use normal (external) assembly to create your own functions (since Visual C++ does not support inline assembly for x86-64 targets).

For example:

_TEXT   SEGMENT

    PUBLIC rdrand32_step
    PUBLIC rdrand32_retry
    PUBLIC rdrand64_step
    PUBLIC rdrand64_retry

    ; int rdrand32_step(unsigned *p)
rdrand32_step PROC
    xor     eax, eax
    rdrand  edx
    ; DB    0fh, 0c7h, 0f2h
    setc    al
    mov     [rcx], edx
    ret
rdrand32_step ENDP

    ; unsigned rdrand32_retry()
rdrand32_retry PROC
retry:
    rdrand  eax
    ; DB    0fh, 0c7h, 0f0h
    jnc     retry
    ret
rdrand32_retry ENDP

    ; int rdrand64_step(unsigned long long *p)
rdrand64_step PROC
    xor     eax, eax
    rdrand  rdx
    ; DB    048h, 0fh, 0c7h, 0f2h
    setc    al
    mov     [rcx], edx
    ret
rdrand64_step ENDP

    ; unsigned long long rdrand64_retry()
rdrand64_retry PROC
retry:
    rdrand  rax
    ; DB    048h, 0fh, 0c7h, 0f0h
    jnc     retry
    ret
rdrand64_retry ENDP

_TEXT   ENDS

    END

If you're using the version of MASM from Visual Studio 2008, you'll probably have to comment out the RDRAND instructions and uncomment the DB directives that follow them.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Ross Ridge
  • 38,414
  • 7
  • 81
  • 112
  • Thank you. Although you tend to be mixing `x86` and `x64` registers. Also don't loop back onto `rdrand` if overflow flag is not set. You may create an infinite loop or "eat up" a lot of CPU cycles. Intel doesn't recommend doing it more than 10 times in a row. Check [4.2.1 Retry Recommendations](https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide) – c00000fd Jun 27 '16 at 06:13
  • 2
    @c00000fd : In 64-bit code there is nothing wrong with using 32-bit registers. In some of his code he's using the fact that when in 64-bit mode you can take advantage of the fact that writing to a 32-bit register automatically zero extends to the entire 64-bit register. This can also save a byte since REX prefixes don't need to be encoded. For example `xor eax, eax` is encoded with one fewer byte than `xor rax, rax`. Ross also returns a 32-bit `int` from his functions so he uses _EDX_ rather than _RDX_. – Michael Petch Jun 27 '16 at 06:44
  • @MichaelPetch: That would be OK for an asm code. I'm assuming mixed code to comply with Microsoft C++ calling conventions which will be broken by neglecting higher 32 bits in registers. – c00000fd Jun 27 '16 at 06:48
  • 2
    @c00000fd The code I wrote is fully compliant with the Microsoft x64 calling conventions. As Michael Petch explained, I didn't neglect the higher 32 bits in registers. Looping back as I did in the `_retry` versions of the functions won't cause an infinite loop unless the CPU is broken, but if you want to implement a counter feel free to expand on my example code. – Ross Ridge Jun 27 '16 at 10:46
0

Wow, it took me a while to figure it out. Here's the steps for Visual Studio 2008 for x64 compilation only:

(A) Create a blank project: File -> New -> Project. Then click on "Visual C++" and select "Empty Project." Name it something, and click OK to create.

(B) Go to your VS installation folder, in my case it was C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\VCProjectDefaults and copy masm.rules file and name it masm64.rules

(C) Open masm64.rules in Notepad and search for Microsoft Macro Assembler and change it to x64 Microsoft Macro Assembler. There will be two places to do it. Then search for ml.exe and change it to ml64.exe. Then save that file and close Notepad.

(D) Right-click your project in "Solution Explorer" and select "Custom build rules" and check x64 Microsoft Macro Assembler and click OK.

(E) Right-click your project in "Solution Explorer" and select Add -> New Item, select Text File (.txt) and name it something with .asm extension. I'll call it funcs_asm_x64.asm. Then click OK.

(F) Open funcs_asm_x64.asm and type your x64 asm. For me I was interested in calling RDRAND with a 64-bit operand. I did the following. This function will take one parameter as a pointer to a 64-bit integer that it will fill out with random bits. It will return 1 in rax if success, otherwise it will return 0.

One thing to remember here is that x64 code uses only __fastcall calling convention, meaning that first 4 parameters for a function are passed in registers: RCX, RDX, R8, and R9:

.code

RdRand64  PROC
    ; RCX = pointer to receive random 64-bit value
    ; RETURN: [RAX] = 1 if success, 0 if failed

    xor         rax, rax

    test        rcx, rcx
    jz          lbl_out

    ;push       rdx
    xor         rdx, rdx
    DB          048h, 0fh, 0c7h, 0f2h       ;RDRAND RDX

    setc        al
    mov         [rcx], rdx
    ;pop        rdx

 lbl_out:
    ret

RdRand64  ENDP

END

(G) Then right-click your project in "Solution Explorer" and select Add -> New Item, select C++ File (.cpp) and name it main.cpp and click OK to create. Then add the following to the main.cpp file:

extern "C" __int64 __fastcall RdRand64(unsigned __int64* pRndVal);

 void main()
{
}

The main part is the extern "C" definition. main() method is needed to satisfy MASM requirements.

(H) Then go to Build -> Configuration Manager and open the drop-down list where it says "Active solution platform" and select New. Then pick "x64" in "Type or select the new platform" and click OK. Then select "x64" as "Active solution platform" and also select "Release" in "Active solution configuration."

(I) Close configuration manager window and build solution. If it succeeded look for funcs_asm_x64.obj file in \x64\Release folder for your solution. Copy that file into your main solution folder (where you needed to use RDRAND instruction.)

(J) Then in your main solution where you need to use RDRAND instruction, right-click on your project in "Solution Explorer" and go to Properties. Then go to Linker -> Command Line and add your obj file name. Obviously do so only for x64 platform for Debug and Release. In my case it was funcs_asm_x64.obj. Click OK to save.

(K) Then to use this function that I just created, first add the extern "C" definition just like you had in the first project:

extern "C" __int64 __fastcall RdRand64(unsigned __int64* pRndVal);

and then you can call it as such (obviously it cannot be inlined):

unsigned __int64 randomNumber = 0;
__int64 bResult = RdRand64(&randomNumber);

(1) Obviously all of the above is not necessary for the Win32 or x86 build. For that simply use the inline assembly like I showed in my original post.

(2) Also obviously you will need to call __cpuid command to ensure that RDRAND instruction is supported. On many CPUs it is still not. So if it is not, then don't call my RdRand64 method, as it will crash! You can use this code to check and store result somewhere in a global variable:

#include <intrin.h>

bool is_RDRAND_supported()
{
    int name[4] = {0};
    __cpuid(name, 0);

    if(name[1] == 0x756e6547 &&         //uneG
        name[2] == 0x6c65746e &&        //letn
        name[3] == 0x49656e69)          //Ieni
    {
        int data[4] = {0};
        __cpuid(data, 1);

        //Check bit 30 on the 2nd index (ECX register)
        if(data[2] & (0x1 << 30))
        {
            //Supported!
            return true;
        }
    }

    return false;
}

(3) There is a way to include the asm file in the same project in VS 2008. Unfortunately if you do that you won't be able to switch the project back to Win32 and compile if you need to. So if you're compiling it only for x64 then save a step and do all of it in the same solution.

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • Since RDX is a volatile register in MS 64-bit calling convention why do you save and restore _RDX_ (with push/pop)? `xor eax, eax` to zero out a register doesn't violate the calling convention. It has the same effect as `xor rax, rax` but takes one less byte (both clear the entire 64-bit register). Same applies to `xor rdx, rdx` – Michael Petch Jun 27 '16 at 07:16
  • @MichaelPetch: Ahhh. Good point. It doesn't need saving. It's just a habit of saving state. – c00000fd Jun 27 '16 at 07:21
0

It's fairly easy, although indirect: Create a tiny C wrapper for _rdrand64_step, compile it into an .OBJ file using VS2012 with no fancy options (No /LTCG, no /Gs etc), and link this object file as-is into your VS2008 project. The VS2008 compiler may not know the instruction, but the VS2008 linker doesn't care.

MSalters
  • 173,980
  • 10
  • 155
  • 350