Inline Assembler for Über Nerds
written by Walter Bright
July 21, 2011
Inline assembler (IA) is the practice of embedding assembly language instructions in a high level programming language's source code. They're 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 the use 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. For 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 there being 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 skool in that a language that calls itself a systems programming language ought to support an inline assembler.
The D programming language specifies that inline assembler must be supported.
Some suggest that inline assembler is obsolete, if one really really needs 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 & 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, and so smoothly integrating with the code generator's usage of registers.
- There are lots of 3rd 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 in them, is zero. It was 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 was 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.
Acknowledgements
Thanks to Andrei Alexandrescu, Bartosz Milewski, and David Held for their helpful comments on a draft of this.