Examples of inline assembly statements (IBM extension)

Example 1: The following example illustrates the usage of the volatile keyword.
#include <stdio.h>

inline bool acquireLock(int *lock){
  bool returnvalue = false;
  int lockval;
  asm volatile(
              /*--------a fence here-----*/
               
               "  0: lwarx %0,0,%2   \n" // Loads the word and reserves
                                         // a memory location for the subsequent
                                         // stwcx. instruction.

               "     cmpwi %0,0      \n" // Compares the lock value to 0.
               "     bne- 1f         \n" // If it is 0, you can acquire the 
                                         // lock. Otherwise, you did not get the
                                         // lock and must try again later.
                                           
               "     ori %0,%0,1     \n" // Sets the lock to 1. 
               "     stwcx. %0,0,%2  \n" // Tries to conditionally store 1
                                         // into the lock word to acquire 
                                         // the lock.
                                           
               "     bne- 0b         \n" // Reservation was lost. Try again.

               "     isync           \n" // Lock acquired. The isync instruction 
                                         // implements an import barrier to 
                                         // ensure that the instructions that
                                         // access the shared region guarded by
                                         // this lock are executed only after 
                                         // they acquire the lock.
 
               "     ori  %1,%1,1    \n" // Sets the return value for the 
                                         // function acquireLock to true.

               "  1:                 \n" // Did not get the lock. 
                                         // Will return false.

               /*------a fence here------*/

               :     "+r"   (lockval),
                     "+r"   (returnvalue) 
               :     "r"    (lock)       // "lock" is the address of the lock in 
                                         // memory.

               :     "cr0"               // cr0 is clobbered by cmpwi and stwcx.
               );
                	         
  return returnvalue;
}
int main()
{
  int myLock;
  if(acquireLock(&myLock)){
       printf("got it!\n");
  }else{ 		
       printf("someone else got it\n");
  } 
  return 0;
}

In this example, %0 refers to the first operand "+r"(lockval), %1 refers to the second operand "+r"(returnvalue), and %2 refers to the third operand "r"(lock).

The assembly statement uses a lock to control access to the shared storage; no instruction can access the shared storage before acquiring the lock.

The volatile keyword implies fences around the assembly instruction group, so that no assembly instructions can be moved out of or around the assembly block.

Without the volatile keyword, the compiler can move the instructions around for optimization. This might cause some instructions to access the shared storage without acquiring the lock.

It is unnecessary to use the memory clobber in this assembly statement, because the instructions do not modify memory in an unexpected way. If you use the memory clobber, the program is still functionally correct. However, the memory clobber results in many unnecessary reloads, imposing a performance penalty.

Example 2: The following example illustrates the use of the symbolic names for input and output operands.
int a ;
int b = 1, c = 2, d = 3 ;  
__asm("  addc %[result], %[first], %[second]" 
      : [result]      "=r"       (a)
      : [first]       "r"        (b), 
        [second]      "r"        (d)
      );

In this example, %[result] refers to the output operand variable a, %[first] refers to the input operand variable b, and %[second] refers to the input operand variable d.

Example 3: The following example shows a typical use of condition registers in the clobbers.
  asm ("   add. %0,%1,%2  \n"     
       :   "=r"    (c)                    
       :   "r"     (a),
           "r"     (b)
       :   "cr0"
       );
In this example, apart from the registers listed in the input and output of the assembly statement, the add. instruction also affects the condition register field 0. Therefore, you must inform the compiler about this by adding cr0 to the clobbers.
Example 4: The following example shows the usage of the memory clobber.
 asm volatile ("   dcbz 0, %0      \n" 
               :   "=r"(b)
               : 
               :   "memory"
               );
In this example, the instruction dcbz clears a cache block, and might have changed the variables in the memory location. There is no way for the compiler to know which variables have been changed. Therefore, the compiler assumes that all data might be aliased with the memory changed by that instruction.

As a result, everything that is needed must be reloaded from memory after the completion of the assembly statement. The memory clobber ensures program correctness at the expense of program performance, because the compiler might reload data that had nothing to do with the assembly statement.

Example 5: The following example shows the usage of the = modifier and the r constraint.
int a ;  
int b = 100 ;  
int c = 200 ;  
asm("   add %0, %1, %2"
    :   "=r"     (a)
    :   "r"      (b),
        "r"      (c)
    );

The add instruction adds the contents of two general purpose registers. The %0, %1, and %2 operands are substituted by the C expressions in the output/input operand fields.

The output operand uses the = modifier to indicate that a modifiable operand is required; it uses the r constraint to indicate that a general purpose register is required. Likewise, the r constraint in the input operands indicates that general purpose registers are required. Within these restrictions, the compiler is free to choose any registers to substitute for %0, %1, and %2.
Note: If the compiler chooses r0 for the second operand, the add instruction uses the literal value 0 and yields an unexpected result. Thus, to prevent the compiler from choosing r0 for the second operand, you can use the b constraint to denote the second operand.
Example 6: The following example shows the usage of the + modifier and the K constraint.
 asm ("   addi %0,%0,%2"
      :   "+r"    (a)
      :   "r"     (a),
          "K"     (15)
      );

This assembly statement adds operand %0 and operand %2, and writes the result to operand %0. The output operand uses the + modifier to indicate that operand %0 can be read and written by the instruction. The K constraint indicates that the value loaded to operand %2 must be an unsigned 16-bit constant value.

Example 7: The following example shows the usage of the % modifier and the f constraint.
asm("   fadd %0, %1, %2"
    :   "=f"    (c)
    :   "%f"    (a),
        "f"     (b)
    ); 
This assembly statement adds operands a and b, and writes the result to operand c. The % modifier indicates that operands a and b can be switched if the compiler can generate better code in doing so. Each operand has the f constraint, which indicates that a floating point register is required.
Example 8: The following example shows the usage of the b constraint.
char res[8]={'a','b','c','d','e','f','g','h'};
char a='y';
int  index=7;

asm ("   stbx %0,%1,%2       \n"     \
     :                               \
     :    "r"    (a),
          "b"    (index),
          "r"    (res)
     ); 
In this example, the b constraint instructs the compiler to choose a general register other than r0 for the input operand %1. The result string of this program is abcdefgy. However, if you use the r constraint and the compiler chooses r0 for %1, this instruction produces an incorrect result string ybcdefgh. For instructions that treat the designation of r0 specially, it is therefore important to denote the input operands with the b constraint.
Example 9: The following example shows the usage of the m constraint.
 asm ("   stb %1,%0       \n"     \
      :   "=m"    (res)           \
      :   "r"     (a)
      );

In this example, the syntax of the instruction stb is stb RS,D(RA), where D is a displacement and R is a register. D+RA forms an effective address, which is calculated from D(RA). By using constraint m, you do not need to manually construct effective addresses by specifying the register and displacement separately.

You can use a single constraint m or o to refer to the two operands in the instruction, regardless of what the correct offset should be and whether it is an offset off the stack or off the TOC (Table of Contents). This allows the compiler to choose the right register (r1 for an automatic variable, for instance) and apply the right displacement automatically.



Voice your opinion on getting help information Ask IBM compiler experts a technical question in the IBM XL compilers forum Reach out to us