Reputation: 8206
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
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
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
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