Inline assembler
Incomputer programming,aninline assembleris a feature of somecompilersthat allows low-level code written inassembly languageto be embedded within a program, among code that otherwise has been compiled from ahigher-level languagesuch asCorAda.
Motivation and alternatives
[edit]The embedding of assembly language code is usually done for one of these reasons:[1]
- Optimization:Programmers can use assembly language code to implement the most performance-sensitive parts of their program'salgorithms,code that is apt to be more efficient than what might otherwise be generated by the compiler.
- Access to processor-specificinstructions:Most processors offer special instructions, such asCompare and SwapandTest and Setinstructions which may be used to constructsemaphoresor other synchronization and locking primitives. Nearly every modern processor has these or similar instructions, as they are necessary to implementmultitasking.Examples of specialized instructions are found in theSPARCVIS,IntelMMXandSSE,andMotorolaAltivecinstruction sets.
- Access to specialcalling conventionsnot yet supported by the compiler.
- System callsand interrupts: High-level languages rarely have a direct facility to make arbitrary system calls, so assembly code is used. Direct interrupts are even more rarely supplied.
- To emit special directives for the linker or assembler, for example to change sectioning, macros, or to make symbol aliases.
On the other hand, inline assembler poses a direct problem for the compiler itself as it complicates the analysis of what is done to each variable, a key part of register allocation.[2]This means the performance might actually decrease. Inline assembler also complicates future porting and maintenance of a program.[1]
Alternative facilities are often provided as a way to simplify the work for both the compiler and the programmer.Intrinsic functionsfor special instructions are provided by most compilers and C-function wrappers for arbitrary system calls are available on everyUnixplatform.
Syntax
[edit]In language standards
[edit]The ISO C++ standard and ISO C standards (annex J) specify a conditionally supported syntax for inline assembler:
An asm declaration has the form
asm-declaration:
asm(string-literal);
The asm declaration is conditionally-supported; its meaning is implementation-defined.[3]
This definition, however, is rarely used in actual C, as it is simultaneously too liberal (in the interpretation) and too restricted (in the use of one string literal only).
In actual compilers
[edit]In practical use, inline assembly operating on values is rarely standalone as free-floating code. Since the programmer cannot predict what register a variable is assigned to, compilers typically provide a way to substitute them in as an extension.
There are, in general, two types of inline assembly supported by C/C++ compilers:
- asm(or__asm__) inGCC.GCC uses a direct extension of the ISO rules: assembly code template is written in strings, with inputs, outputs, and clobbered registers specified after the strings in colons. C variables are used directly while register names are quoted as string literals.[4]
- __asmin MicrosoftVisual C++(MSVC), Borland/Embarcadero C compiler, and descendants. This syntax is not based on ISO rules at all; programmers simply write ASM inside a block without needing to conform to C syntax. Variables are available as if they are registers and some C expressions are allowed.[5]ARM Compilerused to have a similar facility.[6]
The two families of extensions represent different understandings of division of labor in processing inline assembly. The GCC form preserves the overall syntax of the language and compartmentizes what the compiler needs to know: what is needed and what is changed. It does not explicitly require the compiler to understand instruction names, as the compiler is only needed to substitute its register assignments, plus a fewmovoperations, to handle the input requirements. However, the user is prone to specifying clobbered registers incorrectly. The MSVC form of an embeddeddomain-specific languageprovides ease of writing, but it requires the compiler itself to know about opcode names and their clobbering properties, demanding extra attention in maintenance and porting.[7]It is still possible to check GCC-style assembly for clobber mistakes with knowledge of the instruction set.[8]
GNAT (Ada language frontend of the GCC suite), andLLVMuses the GCC syntax.[9][10]TheD programming languageuses a DSL similar to the MSVC extension officially for x86_64,[11]but the LLVM-based LDC also provides the GCC-style syntax on every architecture.[12]MSVC only supports inline assembler on 32-bit x86.[5]
The Rust language has since migrated to a syntax abstracting away inline assembly options further than the LLVM (GCC-style) version. It provides enough information to allow transforming the block into an externally-assembled function if the backend could not handle embedded assembly.[7]
Examples
[edit]A system call in GCC
[edit]Calling an operating system directly is generally not possible under a system using protected memory. The OS runs at a more privileged level (kernel mode) than the user (user mode); a (software)interruptis used to make requests to the operating system. This is rarely a feature in a higher-level language, and sowrapper functionsfor system calls are written using inline assembler.
The following C code example shows an x86 system call wrapper inAT&T assembler syntax,using theGNU Assembler.Such calls are normally written with the aid of macros; the full code is included for clarity. In this particular case, the wrapper performs a system call of a number given by the caller with three operands, returning the result.[13]
To recap, GCC supports bothbasicandextendedassembly. The former simply passes text verbatim to the assembler, while the latter performs some substitutions for register locations.[4]
externinterrno;
intsyscall3(intnum,intarg1,intarg2,intarg3)
{
intres;
__asm__(
"int $0x80"/* make the request to the OS */
:"=a"(res),/* return result in eax ( "a" ) */
"+b"(arg1),/* pass arg1 in ebx ( "b" ) [as a "+" output because the syscall may change it] */
"+c"(arg2),/* pass arg2 in ecx ( "c" ) [ditto] */
"+d"(arg3)/* pass arg3 in edx ( "d" ) [ditto] */
:"a"(num)/* pass system call number in eax ( "a" ) */
:"memory","cc",/* announce to the compiler that the memory and condition codes have been modified */
"esi","edi","ebp");/* these registers are clobbered [changed by the syscall] too */
/* The operating system will return a negative value on error;
* wrappers return -1 on error and set the errno global variable */
if(-125<=res&&res<0){
errno=-res;
res=-1;
}
returnres;
}
Processor-specific instruction in D
[edit]This example of inline assembly from theD programming languageshows code that computes the tangent of x using thex86'sFPU(x87) instructions.
// Compute the tangent of x
realtan(realx)
{
asm
{
fldx[EBP];// load x
fxam;// test for oddball values
fstswAX;
sahf;
jctrigerr;// C0 = 1: x is NAN, infinity, or empty
// 387's can handle denormals
SC18:fptan;
fstpST(0);// dump X, which is always 1
fstswAX;
sahf;// if (!(fp_status & 0x20)) goto Lret
jnpLret;// C2 = 1: x is out of range, do argument reduction
fldpi;// load pi
fxch;
SC17:fprem1;// reminder (partial)
fstswAX;
sahf;
jpSC17;// C2 = 1: partial reminder, need to loop
fstpST(1);// remove pi from stack
jmpSC18;
}
trigerr:
returnreal.nan;
Lret:// No need to manually return anything as the value is already on FP stack
;
}
For readers unfamiliar with x87 programming, thefstsw-sahffollowed by conditional jump idiom is used to access the x87 FPU status word bits C0 and C2.fstswstores the status in a general-purpose register; sahf sets theFLAGS registerto the higher 8 bits of the register; and the jump is used to judge on whatever flag bit that happens to correspond to the FPU status bit.[14]
References
[edit]- ^ab"DontUseInlineAsm".GCC Wiki.Retrieved21 January2020.
- ^Striegel, Ben (13 January 2020).""To a compiler, a blob of inline assembly is like a slap in the face."".Reddit.Retrieved15 January2020.
- ^C++, [dcl.asm]
- ^ab"Extended Asm - Assembler Instructions with C Expression Operands".Using the GNU C Compiler.Retrieved15 January2020.
- ^ab"Inline Assembler".docs.microsoft.
- ^"Migration and Compatibility Guide: Inline assembly with Arm Compiler 6".
- ^abd'Antras, Amanieu (13 December 2019)."Rust RFC-2873: stable inline asm".Retrieved15 January2020.
However it is possible to implement support for inline assembly without support from the compiler backend by using an external assembler instead.
Pull Request for status tracking - ^"⚙ D54891 [RFC] Checking inline assembly for validity".reviews.llvm.org.
- ^"LLVM Language Reference: Inline assembly expressions".LLVM Documentation.Retrieved15 January2020.
- ^"Inline Assembly".Rust Documentation (1.0.0).Retrieved15 January2020.
- ^"Inline Assembler".D programming language.Retrieved15 January2020.
- ^"LDC inline assembly expressions".D Wiki.Retrieved15 January2020.
- ^LinuxProgrammer'sManual– System Calls –
- ^"FSTSW/FNSTSW — Store x87 FPU Status Word".
The FNSTSW AX form of the instruction is used primarily in conditional branching...