This chapter describes support for assembly language programming built into the CodeWarrior compilers. From your C/C++ source code files you use the assembly language features that the CodeWarrior compilers will translate into object code.
This chapter does not discuss the stand-alone assembler available for PowerPC. For information on the stand-alone assembler, see the Assembler Reference.
This chapter does not document all the instructions available
in assembly language. For general information on PowerPC assembly
language instructions, see PowerPC Microprocessor Family: The
Programming Environment for 32-Bit Microprocessors, published
by Motorola (serial number MPCFPE32B/AD).
You can find this and other useful published information on the world-wide web at this address :
http://motorola.com/SPS/PowerPC/teksupport/teklibrary/index.html
The sections in this chapter are:
This section describes how to use the CodeWarrior compiler's built-in support for assembly language programming, including assembler syntax.
The topics in this section are:
To specify that a block of code in your file should be interpreted
as assembly language, use the asm keyword.
NOTE To ensure that the C/C++ compiler recognizes the asm keyword, you must turn off the ANSI Strict and ANSI Keywords Only options in the C/C++ Language settings panel. This panel and its options are fully described
in the IDE User Guide or the C Compilers Reference.
The assembly instructions are the standard PowerPC instruction
mnemonics. For more information on PowerPC assembly language instructions,
see PowerPC Microprocessor Family: The Programming Environment
for 32-Bit Microprocessors, published by Motorola (serial number
MPCFPE32B/AD).
There are two ways to use assembly language with the Metrowerks compilers.
First, you can write code to specify that an entire function is in assembly language. This is called function-level assembly language. Alternatively, assembly statement blocks within a function are also supported. In other words, you can write code that is both in function-level assembly language and statement-level assembly language.
TIP To enter a few lines of assembly language code within a single
function, you can use the compiler's support for intrinsics. Intrinsics
are an alternative to using asm statements within functions. See "Intrinsic Functions."
Function-level assembly code for PowerPC uses the following syntax:
asm {function definition }
asm long MyFunc(void) // OK, an assembly function
{
. . . // assembly instructions
}
However, the following statement-level code is also permitted:
long MyFunc (void)
{
asm {. . .} // inline assembly statement blocks are now supported
}
NOTE Assembly language functions are never optimized, regardless of compiler settings.
Statement-level assembler syntax has the following form:
asm { one or more instructions }
You can use an asm statement wherever a code statement is allowed.
NOTE Functions that contain an asm block are only partially optimized, as the function itself will
be optimized, but the optimizer will skip any asm blocks of code.
The built-in assembler uses all the standard PowerPC assembler
instructions. It accepts some additional directives described
in "Assembler Directives." If you use the machine directive, you can also use instructions that are available only
in certain versions of the PowerPC. For more information, see
"machine."
Keep these tips in mind as you write assembly functions:
Each instruction must end with a newline or a semicolon (;).
Hex constants must be in C-style, not Pascal-style. For example:
li r3, 0xABCDEF // OK li r3, $ABCDEF // ERROR
add r2,r3,r4 // OK ADD R2,R3,R4 // ERROR
blr statement if you use frfree. The compiler does not add one for you. For example:
asm void f(void)
{
fralloc
add r2,r3,r4
frfree
} // SEMANTIC ERROR: No blr statement
asm void g(void)
{
fralloc
add r2,r3,r4
frfree
blr // OK
}
Listing 10.1 shows an example of an assembly function.
Creating an assembly function:
asm void mystrcpy(register char *tostr, register char *fromstr);
asm void mystrcpy(register char *tostr, register char *fromstr)
{
addi tostr,tostr,-1
addi fromstr,fromstr,-1
@1 lbzu r5,1(fromstr)
cmpwi r5,0
stbu r5,1(tostr)
bne @1
}
To set the branch prediction (y) bit for those branch instructions that can use it, use + or -. For example:
@1 bne+ @2 // Predicts branch taken @2 bne- @1 // Predicts branch not taken
Most integer instructions have four different forms:
cr0 to whether the result is less, than, equal to, or greater than
zero. This form ends in a period (".").
SO and OV bits in the XER if the result overflows. This form ends in the letter "o".
o.".
add r3,r4,r5 // Normal add add. r3,r4,r5 // Add with record: sets cr0 addo r3,r4,r5 // Add with overflow:sets XER addo. r3,r4,r5 // Add with overflow and record: sets cr0 and XER
Some instructions only have a record form (with a period). Make sure to include the period always:
andi. r3,r4,7 // '.' is not optional here andis. r3,r4,7 // Or here stwcx. r3,r4,r5 // Or here
The full set of AltiVec assembly instructions is now supported in your inline assembly code. Refer to the manual entitled AltiVec Technology Programming Interface Manual, Rev. 1, on the Motorola web site for more information about AltiVec instructions.
NOTE You would have to specify the machine altivec directive or its equivalent, refer to "machine" for more information.
You can also use intrinsics in your code, refer to "Intrinsic Functions" for more information on this topic.
A label can be any identifier that you haven't already declared
as a local variable. The name may start with @, so these are legal names: foo, @foo, and @1. Only labels that don't start with @ need to end in a colon. For example:
asm void foo(void)
{
x1: add r3,r4,r5 // OK, has colon
@x2: add r6,r7,r8 // OK, has both @ and colon
x3 add r9,r10,r11 // ERROR, Needs colon
@x4 add r12,r13,r14 // OK, starts with @
}
NOTE The first statement in an assembly function cannot be a label
that starts with @.
You cannot begin comments with a pound sign (#), since the preprocessor uses the pound sign. However, you can
use C and C++ comments. For example:
add r3,r4,r5 # ERROR add r3,r4,r5 // OK add r3,r4,r5 /* OK */
You can use all preprocessor features, such as comments and macros,
in the assembler. However you must end each assembly statement
with a semicolon (;), because the preprocessor ignores newlines. For example:
#define remainder(x,y,z) \
divw z,x,y; \
mullw z,z,y; \
subf z,z,x
asm void newPointlessMath(void)
{
remainder(r3,r4,r5)
}
To refer to a memory location, you can use the name of a local variable or argument.
NOTE You can refer to local variables by name even if a function does
not contain the fralloc directive. You can read more about fralloc in the section entitled "Creating a Stack Frame in PowerPC Assembly."
The rule for assigning arguments to registers or memory depends on whether the function has a stack frame. If function has a stack frame, the in-line assembler assigns:
register to r13-r31
register to fp14-fp31
register to r13-r31
register to fp14-fp31
If function has no stack frame, the in-line assembler assigns:
arguments that are declared register and passed in registers to the appropriate register other arguments to memory locations all locals to memory locations
NOTE If there is no stack frame, a function cannot have more than 224 bytes of local variables.
For more information on PowerPC register conventions and argument-passing conventions, see "C and C++ Support for Power PC."
You need to create a stack frame for a function, if the function:
The easiest way to create a stack frame is to use the fralloc directive at the beginning of your function and the frfree directive just before the blr statement at the end. The fralloc directive automatically allocates and de-allocates memory for
local variables and saves and restores the register contents.
Note that frfree can only be used at the end of an assembly function. To exit
a function from the middle, you must branch to frfree at the end of the function (this is exactly how the compiler
generates exit points in the middle of a function) as shown in
the example below.
asm void foo ()
{
fralloc
// Your code here
b theend // to exit from the middle, branch to frfree
// More of your code
theend:
frfree // must specify blr if you use frfree
blr // this instruction returns you to the calling function
}
NOTE If you use frfree you must follow it with a blr instruction. Otherwise you will end up running code from an unspecified
address.
The fralloc directive has an optional argument number which lets you specify
the size in bytes of the parameter area of the stack frame. The
stack frame is an area for storing parameters used by the assembly
code. By default, the compiler creates a 32-byte parameter area
for you to pass variables into your assembly language functions.
If your assembly-language routine calls any function that takes
more than 32 bytes of parameters, you must specify a larger amount.In
PowerPC, function arguments are passed using registers. In the
case of integer values, registers r3-r10 are used. Local variables are where the parameters will be stored
that the registers will point to.
As an example, if you pass 4 long integers to your assembly function, this would consume 16 bytes of the parameter area.
This topic describes how to specify the operands for assembly language instructions. It discusses how to use registers, labels, variable names, and immediate operands.
For a register operand, you must use one of the register names of the appropriate kind for the instruction. The register names are case-sensitive. You can also use a symbolic name for an argument or local variable that was assigned to a register.
The general registers for PowerPC are RTOC, SP, r0 to r31, and gpr0 to gpr31. The floating-point registers are fp0 to fp31 and f0 to f31. The condition registers are cr0 to cr7.
Assembly language mnemonics which take registers as operands which need to access local variables or paramters need to have the parameters or local variables declared as register type:
register int a;
The first twelve parameters of any non-struct vector data type are placed in consecutive vector registers v2 through v13. Any additional vector-typed parameters are passed through memory on the stack. They appear together, 16-byte aligned, and after any non-vector parameters. If fewer (or no) vector type arguments are passed, the unneeded registers are not loaded and will contain undefined values on entry to the called function.
Non-vector parameters are passed in the same registers as they would be if the vector parameters were not present. Structs that contain vector fields are treated the same as any other struct except that they are 16-byte aligned. This can result in words in the parameter list being skipped for alignment (padding) and left with undefined value.
Vector parameters are not shadowed in GPR's. They are not placed in memory unless there are more than 12 vector arguments. Functions that declare a vector data type as a return value place that return value in register v2.
For a label operand, you can use the name of a label. For long
branches (such as b and bl instructions) you can also use function names. For bla and la instructions, you use absolute addresses. For other branches,
you must use the name of a label. For example:
b @3 // OK: Branch to local label b foo // OK: Branch to external function foo bl @3 // OK: Call local label bl foo // OK: Call external function foo bne foo // ERROR: Short branch outside function
You can also use label differencing. addi, subi, li, addis and opword all now support this feature. The syntax goes like this:
<label> `-' <label {`+'|'-' <constant expr>]
label1: addi r5, r5, label2 - label1 + 16 label2:
Whenever an instruction requires a memory location (such as a
load instruction, a store instruction, or la), you can use a local or global variable name. You can modify
local variable names with struct member references, class member
references, array subscripts, or constant displacements. For example,
all of the following are valid local variable references:
asm void foo(void)
{
long myVar;
long myArray[1];
Rect myRectArray[3];
lwz r3,myVar(SP) // load myVar into r3
la r3,myVar(SP) // load address of myVar into r3
lwz r3,myRect.top
lwz r3,myArray[2](SP)
lwz r3,myRectArray[2].top
lbz r3,myRectArray[2].top+1(SP)
}
You may also use a register variable that is a pointer to a struct or class to access a member of the struct. For example:
void foo(void)
{
register Rect *p;
asm {
lwz r3,p->top;
}
}
You can use the @hiword and @loword directives to access parts of a variable defined long long, as shown in Listing 10.2:
long long gTheLongLong = 5;
asm void Foo(void);
asm void Foo(void)
{
fralloc
lwz r5, gTheLongLong@hiword // the upper word of gTheLongLong
lwz r6, gTheLongLong@loword // the lower word of gTheLongLong
frfree // if using frfree, you must specific blr
blr // returns to the calling function
}
This allows you to access the specific part of a register pair.
For an immediate operand, you can use an integer or enum constant,
sizeof expression, and any constant expression using any of the C dyadic
and monadic arithmetic operators. These expressions follow the
same precedence and associativity rules as normal C expressions.
The in-line assembler carries out all arithmetic with 32-bit signed
integers.
An immediate operand can also be a reference to a member of a
struct or class type. You can use any struct or class name from
a typedef statement, followed by any number of member references. This
evaluates to the offset of the member from the start of the struct.
For example:
lwz r4,Rect.top(r3) addi r6,r6,Rect.left
la rD,d(rA)
addi rD,rA,d
You can also use the top or bottom half-word of an immediate word
value as an immediate operand. To do this, use one of the @ modifiers, as illustrated below:
long gTheLong;
asm void foo(void)
{
fralloc
ori r6, gTheLong@ha //upper halfword of address of "gTheLong"
ori r7, gTheLong@h //upper halfword of address of "gTheLong"
addi r7, gTheLong@l //lower halfword of address of "gTheLong"
frfree // if using frfree, you must specify blr
blr // returns to the calling function
}
The preferred technique is exemplified here:
long gTheLong;
asm void foo(void)
{
fralloc
lwz r7,gTheLong(RTOC)
frfree // if using frfree, you must specify blr
blr // returns to the calling function
}
However the access patterns are:
lis x,var@ha la x,var@l(x)
lis x,var@h ori x,x,var@l
In this example, la is the simplified form of addi to load an address. las is like la but shifted. Refer to the Motorola PowerPC manuals for more information.
Using @ha is preferred since you can write:
lis x,var@ha lwz v,var@l(x)
which you can't do with @h because it requires that you use the ori instruction.
This is the simplified form to accessing globals:
void foo(void)
{
register long *addr = &gTheLong;
asm {
.... use addr for r7 ....
}
}
You can directly embed instructions in code using hex numbers as shown here:
asm {
opword 0x60000000 // embed a nop
}
This section describes special assembler directives that the PowerPC built-in assembler accepts. They are:
entry [ extern | static ] name
PowerPC assembler directive that defines an entry point into the
current function. Use the extern qualifier to declare a global entry point and use the static qualifier to declare a local entry point. If you leave out the
qualifier, extern is assumed.
void __save_fpr_15(void);
void __save_fpr_16(void);
asm void __save_fpr_14(void)
{
stfd fp14,-144(SP)
entry __save_fpr_15
stfd fp15,-136(SP)
entry __save_fpr_16
stfd fp16,-128(SP)
// ...
blr // returns to the calling function
}
fralloc [ number ]
PowerPC assembler directive that creates a stack frame for a function and reserves registers for your local register variables. You need to create a stack frame, if the function
You don't need to use fralloc if you use non-volatile registers as long as you save them yourself.
For more information, see "Creating a Stack Frame in PowerPC Assembly."
The fralloc directive has an optional argument number which lets you specify
the size in bytes of the parameter area of the stack frame. By
default, the compiler creates a 32-byte parameter area. If your
assembly-language routine calls any function that takes more than
32 bytes of parameters, you must specify a larger amount.
Using fralloc does not require frfree to be put at the end of functions. If you do use frfree then add blr after it.
Integers are passed using registers like r3, and r4. AltiVec vector arguments are passed differently as discussed
here.
The first twelve parameters of any non-struct vector data type
are placed in consecutive vector registers v2 through v13. Any additional vector-typed parameters are passed through memory
on the stack. They appear together, 16-byte aligned, and after
any non-vector parameters. If fewer (or no) vector type arguments
are passed, the unneeded registers are not loaded and will contain
undefined values on entry to the called function.
Non-vector parameters are passed in the same registers as they
would be if the vector parameters were not present. Structs that
contain vector fields are treated the same as any other struct except that they are 16-byte aligned. This can result in words
in the parameter list being skipped for alignment (padding) and
left with undefined values.
Vector parameters are not shadowed in GPR's. They are not placed in memory unless there are more than 12 vector arguments.
Functions that declare a vector data type as a return value place that return value in register v2.
frfree
PowerPC assembler directive that frees the stack frame and restores
the registers that fralloc reserved. frfree can only be used at the end of an assembly function. To exit
a function from the middle, you must branch to frfree at the end of the function. For more information, see "Creating a Stack Frame in PowerPC Assembly."
NOTE If you use frfree you must follow it with a blr instruction. Otherwise you will end up running code from an unspecified
address.
Using fralloc does not require frfree to be put at the end of functions. If you do use frfree then always add blr after it.
machine number
PowerPC assembler directive that specifies which CPU the assembly code is for. The number must be one of the following:
If you use all, you can use only those instructions that are available on all
PowerPC CPUs. If you don't use the machine directive, the compiler assumes all.
machine altivec
This enables the assembler AltiVec instructions. Note that:
#pragma altivec_codegen on
If you use machine 601, the following instructions, formerly known as the POWER instructions,
are deprecated:
If you use 603 or 604, you may also use the following instructions:
This allows you to suppress prolog generation. In this case you are required to setup the prolog correctly yourself.
asm void foo_asm(void) {
nofralloc
... more code
blr // returns to the calling function
}
This section discusses support for intrinsic functions in the CodeWarrior compilers. Support for intrinsic functions is not part of the ANSI C or C++ standards. They are an extension provided by the CodeWarrior compilers.
Intrinsic functions are one mechanism you can use to get assembly language into your source code. Here's how intrinsic functions work.
There is an intrinsic function for each processor opcode (instruction).
Rather than using inline assembly syntax and specifying the opcode
in an asm block, you call the intrinsic function that matches the opcode.
When the compiler encounters the intrinsic function call in your source code, it doesn't actually make a function call. The compiler substitutes the assembly instruction that matches your function call. As a result, no function call occurs in the final object code. The final code is the assembly language instructions that correspond to the intrinsic functions.
TIP You can use intrinsic functions or the asm keyword to add a few lines of assembly code within a function.
If you want to write an entire function in assembly, you can also
use the inline assembler. See "Working With Assembly."
For additional information on PowerPC assembly language instructions, see PowerPC Microprocessor Family: The Programming Environment for 32-Bit Microprocessors, published by Motorola (#MPCFPE32B/AD R1).
See also: "Working With Assembly."
The topics in this section are:
These functions perform low-level processor synchronization.
void __eieio(void) /* Enforce In-Order Execution of I/O */
void __sync(void) /* Synchronize */
void __isync(void) /* Instruction Synchronize */
For more information on these functions, see the instructions
eieio, sync, and isync in PowerPC Microprocessor Family: The Programming Environments by Motorola.
These functions generate inline instructions that take the absolute value of a number.
int__abs(int); /* Absolute value of an integer */ float __fabs(float); /* Absolute value of a float */ float __fnabs(float); /* Negative absolute value of a float */ long __labs(long); /* Absolute value of a long int */
These functions generate inline instructions than can dramatically speed up certain code sequences, especially byte-reversal operations:
int __lhbrx(void *, int); /* Load halfword byte - reverse index */ int __lwbrx(void *, int); /* Load word byte - reverse index */ void __sthbrx(unsigned short, void *, int); /* Store halfword byte - reverse index */ void __stwbrx(unsigned int, void *, int); /* Store word byte - reverse indexed */
However, these intrinsics are now created as having side effects and will never be optimized away.
This function lets you change the PowerPC processor's Floating Point Status and Control Register (FPSCR). It sets the FPSCR to its argument and returns the original value of the FPSCR.
float __setflm(float);
This example shows how to set and restore the FPSCR:
double old_fpscr; oldfpscr = __setflm(0.0); /* Clear all flag/exception/mode bits and save the original settings */ /* Peform some floating point operations */ __setflm(old_fpscr); /* Restore the FPSCR */
These functions rotate the contents of a variable to the left.
int __rlwinm(int, int, int, int); /* Rotate Left Word Immediate, then AND with Mask */ int __rlwnm(int, int, int, int); /* Rotate Left Word, then AND with Mask */ int __rlwimi(int, int, int, int, int); /* Rotate Left Word Immediate then Mask Insert */
Please note that the first argument to __rlwimi is usually overwritten. However, if the first parameter is a
local variable allocated to a register, then it is both an input
and output parameter. For this reason, this intrinsic should always
be written to put the result in the same variable as the first
parameter as shown here:
ra = __rlwimi( ra, rs, sh, mb, me );
You may count the leading zeros in a register with this one:
int __cntlzw(int); /* Count leading zeros in a integer */
TIP You can use inline assembly for a complete assembly language function, as well as individual assembly statements. See "Working With Assembly."
__rlwinm When the __rlwimi intrinsic is used within an inline function it can violate C
syntax by modifying the first parameter passed by value if its
stored in a register. Because of this non-conformist behavior,
there is no good way to modify the C compiler to allow maximum
benefit of the intrinsic. Since this behavior can also be not
obvious, it can cause problems when writing your code.
Specifically, if an inline function calls __rlwini and it does not want the input parameter to the function overwritten,
the user should assign the input to the result before calling
__rlwini. To prevent the possible overwriting of the input value they
must do something like the following:
int inline mask_insert(int input, int value)
{
int Result = input;
result = __rlwimi(result, value, 0, 16, 31);
return(result);
}
The reason for having this nasty instrinsic is so you can use the side effect:
int inline swapbytes(int input)
{
int result = __rlwinm(input,8,24,31);
result = __rlwimi(result,input,24,16,23);
result = __rlwimi(result,input,8,8,15);
result = __rlwimi(result,input,24,0,7);
return(result);
}
There is no way for the inliner to know that __rlwimi is an invalid function so it does copy propagation (to remove
wasted copies). The only way to make this example work right is
to force __rlwimi() to copy it's input value into a temp register which will hold
the result. This however will break the code of people who have
not updated their source to use the correct form.
Instead of depending upon the __rlwimi side-effect of value overwriting by doing this:
int inline swapbytes(int input)
{
int result = __rlwinm(input,8,24,31);
__rlwimi(result,input,24,16,23);
__rlwimi(result,input,8,8,15);
__rlwimi(result,input,24,0,7);
return(result);
}
You should be using the statement level assembler like this:
int inline mask_insert(register int input)
{
register int value = 0xC0DE;
asm {
rlwimi input, value, 0, 16, 31
}
return input;
}
To prevent the problem from ever appearing.
The intrinsics shown in Table 10.1 map directly to PowerPC assembly instructions.
Instrinsic Prototype
PowerPC Instruction
The intrinsics shown in Table 10.2 map directly to PowerPC assembly instructions.
| Intrinsic Prototype |
PowerPC Instruction |
|---|---|
Some intrinsics allow control over areas of memory, so you can manipulate memory blocks.
void *__alloca(ulong);
__alloca implements alloca() in the compiler.
char *__strcpy(char *, const char *);
__strcpy() detects copies of constant size and calls __memcpy(). This intrinsic requires that a __strcpy function be implemented because if the string is not a constant
it will call __strcpy to do the copy.
void *__memcpy(void *, const void *, size_t);
__memcpy() provides access to the block move in the code generator to do
the block move inline.
You can use many AltiVec intrinsics in your code as you would any other intrinsic. You will find a list of the supported intrinsics in the relevant Motorola documentation at this URL on the world-wide web:
http://www.mot.com/SPS/PowerPC/teksupport/teklibrary/manuals/altivecpim.pdf
A table of these intrinsics is shown here as Table 10.3 and Table 10.4 for reference.
NOTE Note that you can't overload the names. Other specifications for the AltiVec programming interface may refer to overloading, which is not supported in this implementation.