Examples of inline assembly statements (IBM extension)
#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.
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.
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. 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.
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.
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.
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.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. 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.