Jump to content

Inline assembler

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Artoria2e5 (talk | contribs) at 03:01, 15 January 2020 (In actual compilers). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

In computer programming, an inline assembler is a feature of some compilers that allows low-level code written in assembly language to be embedded within a program, among code that otherwise has been compiled from a higher-level language such as C or Ada.

Motivation and alternatives

The embedding of assembly language code is usually done for one of three reasons:

  • Optimization: Programmers can use assembly language code to implement the most performance-sensitive parts of their program's algorithms, code that is apt to be more efficient than what might otherwise be generated by the compiler.
  • Access to processor specific instructions: Most processors offer special instructions, such as Compare and Swap and Test and Set instructions which may be used to construct semaphores or other synchronization and locking primitives. Nearly every modern processor has these or similar instructions, as they are necessary to implement multitasking. Examples of specialized instructions are found in the SPARC VIS, Intel MMX and SSE, and Motorola Altivec instruction sets.
  • System calls: High-level languages rarely have a direct facility to make arbitrary system calls, so assembly code is used.

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. Alternative facilities are often provided as a way to simplify the work for both the compiler and the programmer. Intrinsic functions for special instructions are provided by most compilers and C-function wrappers for system calls are available on every Unix platform.

Syntax

In language standards

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-definition:
    asm ( string-literal ) ;
    The asm declaration is conditionally-supported; its meaning is implementation-defined.

This definition, however, is rarely used in actual C, as it is simutaneously too liberal (in the interpretation) and too restricted (in the use of one string literal only).

In actual compilers

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__) in GCC. 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.[1]
  • __asm in Microsoft Visual C++ (MSVC) and descendents. This syntax is not based on ISO rules at all; programmers simply write ASM inside a block without needing to conform to any C syntax. Variables are available as if they are registers.[2] This feature is not available on x86_64 or ARM versions of MSVC.

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 needs to substitute in its register assignments plus a few mov operations to handle the input requirements. The MSVC form provides some ease of writing, but it requires the compiler itself to know about opcode names and their clobbering properties, demanding extra attention in maintence and porting.

GNAT (Ada language frontend of the GCC suite), LLVM, and the Rust programming language uses a syntax similar to the GCC syntax.[3][4] The D programming language uses a syntax similar to the MSVC extension officially for x86_64,[5] but the LLVM-based LDC also provides the GCC-style syntax on every architecture.[6]

Examples

A system call in GCC

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) interrupt is used to make requests to the operating system. This is rarely a feature in a higher-level language, and so wrapper functions for system calls are written using inline assembler.

The following C code example shows an x86 system call wrapper in AT&T assembler syntax, using the GNU 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.[7]

To recap, GCC supports both basic and extended assembly. The former simply passes text verbatim to the assembler, while the latter performs some substitutions for register locations.[1]

extern int errno;

int syscall3(int num, int arg1, int arg2, int arg3)
{
  int res;
  __asm__ volatile (
    "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 */

  /* 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;
  }
  return res;
}

Processor-specific instruction in D

This example of inline assembly from the D programming language shows code that computes the tangent of x using the x86's FPU (x87) instructions.

// Compute the tangent of x
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                           ; // load pi
       fxch                            ;
SC17:  fprem1                          ;
       fstsw   AX                      ;
       sahf                            ;
       jp      SC17                    ;
       fstp    ST(1)                   ; // remove pi from stack
       jmp     SC18                    ;
   }
trigerr:
   return real.nan;
Lret:
   ;
}

References

  1. ^ a b "Extended Asm - Assembler Instructions with C Expression Operands". Using the GNU C Compiler. Retrieved 15 January 2020.
  2. ^ "Inline Assembler". docs.microsoft.com.
  3. ^ "LLVM Language Reference: Inline assembly expressions". LLVM Documentation. Retrieved 15 January 2020.
  4. ^ "Inline Assembly". Rust Documentation (1.0.0). Retrieved 15 January 2020.
  5. ^ "Inline Assembler". D programming language. Retrieved 15 January 2020.
  6. ^ "LDC inline assembly expressions". D Wiki. Retrieved 15 January 2020.
  7. ^ syscall(2) – Linux Programmer's Manual – System Calls