Search code examples
cassemblyx86-64

Zero-cost integer upcast via inline assembly


Is it possible to manufacture, as a macro, a sort of zero-cost integer upcast via inline assembly?

In many instances, I would like to pass 32 bit integers, which are already "in place" through interfaces that take 64 bit integers. But C doesn't like leaving upper bits indeterminate and casting or trying to extend the 32 bits to 64 via a union makes compilers either zero-extend or sign-extend the 32 bit number.

Dummy example:

void g(unsigned long );
void f(int X){
    unsigned long XX = (unsigned)X; //mov %edi, %edi
    g(XX);
}

Is it possible to replace the cast with some JUST_PRETEND_ITS_AN_UNSIGNED_LONG_WITHOUT_DOING_ANYTHING(X)?


Solution

  • A combo of unions and inline assembly matching constraints seems to have done it:

    #include <stdint.h>
    void takeUptr(uintptr_t U);
    #define upcast(X)  ({ union { int x; uintptr_t xx; } u; u.x = X; \
        asm("":"=r"(u.xx):"0"(u.x)); /*make C think the upper bits are inited*/ \
        u.xx; })
    
    void passAsUptr(int x){ takeUptr(upcast(x)); } 
    //^jmp takeUptr; //no zero/sign-extending mov ✓
    uintptr_t retAsUptr(int x){ return upcast(x); } 
    //^movl %edi, %eax; ret; //correctly moved to the output register ✓
    

    https://godbolt.org/z/vz9W5Mq8M

    On gcc (sadly not clang) this eliminates the zero-extending mov when moving from a 32-bit to a 64-bit number when they're both mapped to the same register. The key was using the positional 0 constraint to make the register of the input (here u.x) match the register of the output (u.xx, at position 0, hence constraint 0).