Jonathan Henson
Jonathan Henson

Reputation: 8206

inline AT&T asm syntax for using opcode directly instead of mnemonic

For unfortunate reasons I can't get into, I have to support an ancient assembler that doesn't have a mapping for a mnemonic I need.

I know the hardware supports it, but I can't seem to find any documentation online for how to use an opcode instead of a mnemonic.

Does anyone have a reference for how to do it in inline AT&T syntax on GCC.

Upvotes: 2

Views: 1156

Answers (3)

fuz
fuz

Reputation: 93082

As of binutils 2.41, a new directive .insn permits the generation of arbitrary instructions. While this may not help you and your old assembler in particular, it may be useful for future readers encountering this question. For the example of rdrand, this could look as such:

#define rdrand(x) asm (".insn 0x0fc7/6, %0" : "=r"(x))

The .insn directive automatically figures out which encoding and instruction size to use based on the operand given, so we can use a macro like the above for all operand sizes, even if memory operands were present (though for rdrand these are not allowed of course). For example,

long long example(void)
{
    char foo;
    short bar;
    int baz;
    long long quux;

    rdrand(foo);
    rdrand(bar);
    rdrand(baz);
    rdrand(quux);

    return (foo + bar + baz + quux);
}

becomes

0000000000000000 <example>:
   0:   0f c7 f0                rdrand %eax
   3:   66 0f c7 f2             rdrand %dx
   7:   0f be c0                movsbl %al,%eax
   a:   0f bf d2                movswl %dx,%edx
   d:   01 d0                   add    %edx,%eax
   f:   0f c7 f6                rdrand %esi
  12:   01 f0                   add    %esi,%eax
  14:   48 98                   cltq
  16:   48 0f c7 f1             rdrand %rcx
  1a:   48 01 c8                add    %rcx,%rax
  1d:   c3  

                ret

You can see how .insn is not able to tell apart 8-bit and 32-bit operands as the x86 instruction encoding realises 8-bit instructions with separate opcodes instead of a prefix. This may lead to invalid results. If desired, an assertion can be added to avoid this case:

#define rdrand(x) do { \
    _Static_assert(sizeof (x) > sizeof (char)); \
    asm (".insn 0x0fc7/6, %0" : "=r"(x)); \
    } while (0)

Upvotes: 2

Jester
Jester

Reputation: 58782

Luckily rdrand only takes a single argument and that is a register. As such you only need to cover a few cases if you want to allow the compiler to choose freely. Beware, it's still quite ugly :)

inline int rdrand()
{
    int result;
    __asm__ __volatile__ (
        ".byte 0x0f, 0xc7\n\t"
        ".ifc %0, %%eax\n\t"
        ".byte 0xf0\n\t"
        ".else\n\t"
        ".ifc %0, %%ebx\n\t"
        ".byte 0xf3\n\t"
        ".else\n\t"
        ".ifc %0, %%ecx\n\t"
        ".byte 0xf1\n\t"
        ".else\n\t"
        ".ifc %0, %%edx\n\t"
        ".byte 0xf2\n\t"
        ".else\n\t"
        ".ifc %0, %%esi\n\t"
        ".byte 0xf6\n\t"
        ".else\n\t"
        ".ifc %0, %%edi\n\t"
        ".byte 0xf7\n\t"
        ".else\n\t"
        ".ifc %0, %%ebp\n\t"
        ".byte 0xf5\n\t"
        ".else\n\t"
        ".error \"uknown register\"\n\t"
        ".endif\n\t"
        ".endif\n\t"
        ".endif\n\t"
        ".endif\n\t"
        ".endif\n\t"
        ".endif\n\t"
        ".endif\n\t"
    : "=R" (result) : : "cc");

    // "=R" excludes r8d..r15d in 64-bit mode
    return result;
}

For 64-bit operand-size, you'll need a REX.W (0x48) prefix, but the "=R" constraint instead of "=r" will avoid needing any other bits set in the REX prefix.

Note that rdrand also uses the carry flag the handling for which is left as an exercise for the reader. gcc6 can use flag output operands, which is more efficient than setcc.

Upvotes: 3

fuz
fuz

Reputation: 93082

Try this:

long result;
char success = 0; /* make sure we don't get surprised by setc only writing 8 bits */

/* "rdrand %%rax ; setc %b1" */
asm volatile (".byte 0x48, 0x0f, 0xc7, 0xf0; setc %b1" : "=a"(result), "=qm"(success) :: "cc");

The a constraint forces the compiler to use the rax register for result. This is as general as it gets without being obnoxious. I suggest you to add a configure test to check if the assembler understands rdrand and use this code:

long result;
char success = 0;

#ifdef HAVE_RDRAND
asm volatile ("rdrand %0; setc %b1" : "=r"(result), "=qm"(success) :: "cc");
#else
asm volatile (".byte 0x48, 0x0f, 0xc7, 0xf0; setc %b1" : "=a"(result), "=qm"(success) :: "cc");
#endif

While there might be a tiny performance penalty in forcing the compiler to use the rax register if the assembler does not understand rdrand, it is far outweighted by the complicated kludges needed to allow any register to be used.

Upvotes: 4

Related Questions