Inline Assembler for Über Nerds
Inline assembler (IA) is the practice of embedding assembly language instructions in a high-level programming language's source code, and is an early example of an EDSL (Embedded Domain Specific Language).
IA isn't needed that often if the host programming language is powerful, but it is used to:
- Generate special instructions, like locking code for multithreaded synchronization
- Access special CPU features, such as debug registers
- Super optimize a very critical section of code
- Ease into learning assembler
- Be an intermediate step when translating large assembly language projects into a high-level language
- Build thunks or access code that has special calling conventions
- Interface to hardware requiring specific instruction sequences to access
Add a special point 8 for me, as I get an endorphin rush from writing a particularly crafty bit of assembler. Yes, I am a nerd.
Look over the source to an operating system or a device driver, and you're likely to find a sprinkling of inline assembler here and there.
Here's a simple example of inline assembler in the D programming language to use the Intel RDTSC instruction to read the clock count into the EDX and EAX registers:
long clock()
{
asm
{
RDTSC;
}
}
Long's are returned in EDX and EAX, so this works out famously. Here's a somewhat longer example to compute the tangent of x using the FPU instructions:
real tan(real x)
{
asm
{
fld x[EBP] ; // load x
fxam ; // test for oddball values
fstsw AX ;
sahf ;
jc trigerr ; // x is NAN, infinity, or empty
// 387's can handle denormals
SC18:
fptan ;
fstp ST(0) ; // dump X, which is always 1
fstsw AX ;
sahf ;
jnp Lret ; // C2 = 1 (x is out of range)
// Do argument reduction to bring x into range
fldpi ;
fxch ;
SC17:
fprem1 ;
fstsw AX ;
sahf ;
jp SC17 ;
fstp ST(1) ; // remove pi from stack
jmp SC18 ;
}
trigerr:
return real.nan;
Lret:
;
}
Despite thousands of different programming languages, only a relative handful support inline assembler. Surprisingly, even C and C++ don't officially have inline assemblers — they are done as extensions by compiler vendors. A C and C++ compiler can be 100% standard compliant with no inline assembler, and these do exist, such as Microsoft VC++ for Win64. I'm a bit old school: A language that calls itself a systems programming language ought to support an inline assembler.
The D programming language specifies that an inline assembler must be supported.
Some suggest that inline assemblers are obsolete; if one really needs an assembler, just use a separate assembler. There are lots of great assemblers out there. Why not?
I abandoned using separate assemblers years ago for the following reasons:
- Poor (i.e. zero) integration with the compiler.
- You have to rewrite your data structure and manifest constant declarations in the assembler, and of course these always get out of sync with the ones in your D source.
- Having the compiler set up the call/return sequences and parameter addressing is so darned convenient.
- The compiler will keep track of register usage for you — which registers are read or written — smoothly integrating with the code generator's usage of registers.
- There are lots of third-party assemblers, all different. Even the same assembler will have multiple versions. The chances of the asm source assembling on all of them, and avoiding all the various bugs, are zero. It is a major tech support issue.
- It really hurts my brain to have gas (Gnu ASsembler) swap the order of the operands.
- gas doesn't follow the Intel syntax, so you have to do a mental translation from the Intel datasheets to the gas source. gas doesn't even use the same instruction names.
- External assemblers don't do name mangling. You've got to do it all manually. This is a horror.
- Symbolic debug formats differ.
- Having to manage a separate source file for just two instructions is highly annoying.
- Writing an inline assembler isn't hard. There's nothing terribly clever about it.
Getting the assembler integrated into the compiler made me much more productive and my life much easier. It was a giant win for this nerd, no doubt about it. You can pry my inline assembler from my cold, dead fingers.
Thanks to Andrei Alexandrescu, Bartosz Milewski, and David Held for their helpful comments on a draft of this post.

