MIPS Register Conventions : Focus on Embedded dustLiYang
http://blog.csdn.net/sdustliyang/article/details/46558849
For development on a CPU, it is very important to master the CPU's register conventions for its operation.
The MIPS architecture provides 32 GPRs (GENERAL PURPOSE REGISTER). The usage of these 32 registers is roughly as follows:
REGISTER NAME USAGE
$0 $zero constant value 0 (constant value 0)
$2-$3 $v0-$v1 values for results and expression evaluation (values for results and expression evaluation)
$4-$7 $a0-$a3 arguments (arguments)
$8-$15 $t0-$t7 temporary variables. When used in subroutines, no need to save, from the perspective of the upper function, the values of the registers may change before and after the function call.
$16-$23 $s0-$s7 variables that need to be saved. When used in subroutines, the original values must be saved and restored before returning. From the perspective of the upper function, the values of these registers do not change.
$24-$25 $t8-$t9 temporary variables
$26-$27 $k0-$k1 reserved for interrupt or trap handlers; thus values may change at any time
$28 $gp Global Pointer (Global Pointer). Using gp as the base address, for data access within a 32K range before and after the gp pointer, only one instruction is needed to complete. The usual practice is to place some small global data (static extern), etc., together, using gp as the pointer.
$29 $sp Stack Pointer (Stack Pointer). MIPS usually only adjusts the stack pointer at the exit and entry of subroutines during multiple calls, and this process is handled by the subroutine.
$30 $fp Frame Pointer (Frame Pointer) (BNN: fp is stale actually, and can be simply used as $t8)
$31 $ra return address (return address)
The correct usage of the register convention for a CPU is very important. Of course, C language developers don't need to care because the COMPILER will TAKE CARE. But for those developing KERNEL or DRIVER, it is **necessary** to be clear.
Generally speaking, you can clearly see the usage of registers through objdump -d.
The following explains through a simple example I just wrote:
~/ vi Hello.c
"Hello.c"
/* Example to illustrate mips register convention
* -Author: BNN
* 11/29/2001
*/
int addFunc(int,int);
int subFunc(int);
void main()
{
int x,y,z;
x= 1;
y=2;
z = addFunc(x,y);
}
int addFunc(int x,int y)
{
int value1 = 5;
int value2;
value2 = subFunc(value1);
return (x+y+value2);
}
int subFunc(int value)
{
return value--;
}
The above is a C program. The main() function calls an addition sub-function. Let's see how the compiler generates code.
~/bnn:74> /bin/mips-elf-gcc -c Hello.o Hello.c -mips3 -mcpu=r4000 -mgp32 -mfp32 -O1
~/bnn:75> /bin/mips64-elf-objdump -d Hello.o
Hello.o: file format elf32-bigmips
Disassembly of section .text:
/* main Function */
0000000000000000 :
/*create a stack frame by moving the stack pointer 8
*bytes down and meantime update the sp value
*/
0: 27bdfff8 addiu $sp,$sp,-8
/* Save the return address to the current sp position.*/
4: afbf0000 sw $ra,0($sp)
8: 0c000000 jal 0
/* nop is for the delay slot */
c: 00000000 nop
/* Fill the argument a0 with the value 1 */
10: 24040001 li $a0,1
/* Jump the addFunc */
14: 0c00000a jal 28
/* NOTE HERE: Why we fill the second argument
*behind the addFunc function call?
* This is all about the "-O1" compilation optimizaiton.
* With mips architecture, the instruciton after jump
* will also be fetched into the pipline and get
* exectuted. Therefore, we can promise that the
* second argument will be filled with the value of
* integer 2.
*/
18: 24050002 li $a1,2
/*Load the return address from the stack pointer
* Note here that the result v0 contains the result of
* addFunc function call
*/
1c: 8fbf0000 lw $ra,0($sp)
/* Return */
20: 03e00008 jr $ra
/* Restore the stack frame */
24: 27bd0008 addiu $sp,$sp,8
/* addFunc Function */
0000000000000028 :
/* Create a stack frame by allocating 16 bytes or 4
* words size
*/
28: 27bdfff0 addiu $sp,$sp,-16
/* Save the return address into the stack with 8 bytes
* offset. Please note that compiler does not save the
* ra to 0($sp).
*Think of why, in contrast of the previous PowerPC
* EABI convention
*/
2c: afbf0008 sw $ra,8($sp)
/* We save the s1 reg. value into the stack
* because we will use s1 in this function
* Note that the 4,5,6,7($sp) positions will then
* be occupied by this 32 bits size register
*/
30: afb10004 sw $s1,4($sp)
/* Withe same reason, save s0 reg. */
34: afb00000 sw $s0,0($sp)
/* Retrieve the argument 0 into s0 reg. */
38: 0080802d move $s0,$a0
/* Retrieve the argument 1 into s1 reg. */
3c: 00a0882d move $s1,$a1
/* Call the subFunc with a0 with 5 */
40: 0c000019 jal 64
/* In the delay slot, we load the 5 into argument a0 reg
*for subFunc call.
*/
44: 24040005 li $a0,5
/* s0 = s0+s1; note that s0 and s1 holds the values of
* x,y, respectively
*/
48: 02118021 addu $s0,$s0,$s1
/* v0 = s0+v0; v0 holds the return results of subFunc
*call; And we let v0 hold the final results
*/
4c: 02021021 addu $v0,$s0,$v0
/*Retrieve the ra value from stack */
50: 8fbf0008 lw $ra,8($sp)
/*!!!!restore the s1 reg. value */
54: 8fb10004 lw $s1,4($sp)
/*!!!! restore the s0 reg. value */
58: 8fb00000 lw $s0,0($sp)
/* Return back to main func */
5c: 03e00008 jr $ra
/* Update/restore the stack pointer/frame */
60: 27bd0010 addiu $sp,$sp,16
/* subFunc Function */
0000000000000064 :
/* return back to addFunc function */
64: 03e00008 jr $ra
/* Taking advantage of the mips delay slot, filling the
* result reg v0 by simply assigning the v0 as the value
*of a0. This is a bug from my c source
* codes--"value--". I should write my codes
* like "--value", instead.
68: 0080102d move $v0,$a0
Hope everyone calms down and understands the above code. Be sure to pay attention to why the compiler saves s0 and s1 first before using them, and then restores them. Although in this example, the main function doesn't use s0 and s1.
Another point is: because we added "-O1" optimization, the compiler uses the "delay slot" to execute those instructions that must be executed, instead of simply putting a "nop" instruction there. Very beautiful.
Finally, test everyone a question to make everyone understand the usage of registers better:
*When writing a core scheduling context switch() routine, do we need to SAVE/RESTORE $t0-$t7? If not, why?
*When writing a clock interrupt handling routine, do we need to SAVE/RESTORE $t0-$t7? If yes, why?
This article comes from the CSDN blog. Please indicate the source when reprinting: http://blog.csdn.net/yoursy/archive/2008/05/06/2399721.aspx
[ Last edited by zzz19760225 on 2017-11-2 at 00:38 ]
http://blog.csdn.net/sdustliyang/article/details/46558849
For development on a CPU, it is very important to master the CPU's register conventions for its operation.
The MIPS architecture provides 32 GPRs (GENERAL PURPOSE REGISTER). The usage of these 32 registers is roughly as follows:
REGISTER NAME USAGE
$0 $zero constant value 0 (constant value 0)
$2-$3 $v0-$v1 values for results and expression evaluation (values for results and expression evaluation)
$4-$7 $a0-$a3 arguments (arguments)
$8-$15 $t0-$t7 temporary variables. When used in subroutines, no need to save, from the perspective of the upper function, the values of the registers may change before and after the function call.
$16-$23 $s0-$s7 variables that need to be saved. When used in subroutines, the original values must be saved and restored before returning. From the perspective of the upper function, the values of these registers do not change.
$24-$25 $t8-$t9 temporary variables
$26-$27 $k0-$k1 reserved for interrupt or trap handlers; thus values may change at any time
$28 $gp Global Pointer (Global Pointer). Using gp as the base address, for data access within a 32K range before and after the gp pointer, only one instruction is needed to complete. The usual practice is to place some small global data (static extern), etc., together, using gp as the pointer.
$29 $sp Stack Pointer (Stack Pointer). MIPS usually only adjusts the stack pointer at the exit and entry of subroutines during multiple calls, and this process is handled by the subroutine.
$30 $fp Frame Pointer (Frame Pointer) (BNN: fp is stale actually, and can be simply used as $t8)
$31 $ra return address (return address)
The correct usage of the register convention for a CPU is very important. Of course, C language developers don't need to care because the COMPILER will TAKE CARE. But for those developing KERNEL or DRIVER, it is **necessary** to be clear.
Generally speaking, you can clearly see the usage of registers through objdump -d.
The following explains through a simple example I just wrote:
~/ vi Hello.c
"Hello.c"
/* Example to illustrate mips register convention
* -Author: BNN
* 11/29/2001
*/
int addFunc(int,int);
int subFunc(int);
void main()
{
int x,y,z;
x= 1;
y=2;
z = addFunc(x,y);
}
int addFunc(int x,int y)
{
int value1 = 5;
int value2;
value2 = subFunc(value1);
return (x+y+value2);
}
int subFunc(int value)
{
return value--;
}
The above is a C program. The main() function calls an addition sub-function. Let's see how the compiler generates code.
~/bnn:74> /bin/mips-elf-gcc -c Hello.o Hello.c -mips3 -mcpu=r4000 -mgp32 -mfp32 -O1
~/bnn:75> /bin/mips64-elf-objdump -d Hello.o
Hello.o: file format elf32-bigmips
Disassembly of section .text:
/* main Function */
0000000000000000 :
/*create a stack frame by moving the stack pointer 8
*bytes down and meantime update the sp value
*/
0: 27bdfff8 addiu $sp,$sp,-8
/* Save the return address to the current sp position.*/
4: afbf0000 sw $ra,0($sp)
8: 0c000000 jal 0
/* nop is for the delay slot */
c: 00000000 nop
/* Fill the argument a0 with the value 1 */
10: 24040001 li $a0,1
/* Jump the addFunc */
14: 0c00000a jal 28
/* NOTE HERE: Why we fill the second argument
*behind the addFunc function call?
* This is all about the "-O1" compilation optimizaiton.
* With mips architecture, the instruciton after jump
* will also be fetched into the pipline and get
* exectuted. Therefore, we can promise that the
* second argument will be filled with the value of
* integer 2.
*/
18: 24050002 li $a1,2
/*Load the return address from the stack pointer
* Note here that the result v0 contains the result of
* addFunc function call
*/
1c: 8fbf0000 lw $ra,0($sp)
/* Return */
20: 03e00008 jr $ra
/* Restore the stack frame */
24: 27bd0008 addiu $sp,$sp,8
/* addFunc Function */
0000000000000028 :
/* Create a stack frame by allocating 16 bytes or 4
* words size
*/
28: 27bdfff0 addiu $sp,$sp,-16
/* Save the return address into the stack with 8 bytes
* offset. Please note that compiler does not save the
* ra to 0($sp).
*Think of why, in contrast of the previous PowerPC
* EABI convention
*/
2c: afbf0008 sw $ra,8($sp)
/* We save the s1 reg. value into the stack
* because we will use s1 in this function
* Note that the 4,5,6,7($sp) positions will then
* be occupied by this 32 bits size register
*/
30: afb10004 sw $s1,4($sp)
/* Withe same reason, save s0 reg. */
34: afb00000 sw $s0,0($sp)
/* Retrieve the argument 0 into s0 reg. */
38: 0080802d move $s0,$a0
/* Retrieve the argument 1 into s1 reg. */
3c: 00a0882d move $s1,$a1
/* Call the subFunc with a0 with 5 */
40: 0c000019 jal 64
/* In the delay slot, we load the 5 into argument a0 reg
*for subFunc call.
*/
44: 24040005 li $a0,5
/* s0 = s0+s1; note that s0 and s1 holds the values of
* x,y, respectively
*/
48: 02118021 addu $s0,$s0,$s1
/* v0 = s0+v0; v0 holds the return results of subFunc
*call; And we let v0 hold the final results
*/
4c: 02021021 addu $v0,$s0,$v0
/*Retrieve the ra value from stack */
50: 8fbf0008 lw $ra,8($sp)
/*!!!!restore the s1 reg. value */
54: 8fb10004 lw $s1,4($sp)
/*!!!! restore the s0 reg. value */
58: 8fb00000 lw $s0,0($sp)
/* Return back to main func */
5c: 03e00008 jr $ra
/* Update/restore the stack pointer/frame */
60: 27bd0010 addiu $sp,$sp,16
/* subFunc Function */
0000000000000064 :
/* return back to addFunc function */
64: 03e00008 jr $ra
/* Taking advantage of the mips delay slot, filling the
* result reg v0 by simply assigning the v0 as the value
*of a0. This is a bug from my c source
* codes--"value--". I should write my codes
* like "--value", instead.
68: 0080102d move $v0,$a0
Hope everyone calms down and understands the above code. Be sure to pay attention to why the compiler saves s0 and s1 first before using them, and then restores them. Although in this example, the main function doesn't use s0 and s1.
Another point is: because we added "-O1" optimization, the compiler uses the "delay slot" to execute those instructions that must be executed, instead of simply putting a "nop" instruction there. Very beautiful.
Finally, test everyone a question to make everyone understand the usage of registers better:
*When writing a core scheduling context switch() routine, do we need to SAVE/RESTORE $t0-$t7? If not, why?
*When writing a clock interrupt handling routine, do we need to SAVE/RESTORE $t0-$t7? If yes, why?
This article comes from the CSDN blog. Please indicate the source when reprinting: http://blog.csdn.net/yoursy/archive/2008/05/06/2399721.aspx
[ Last edited by zzz19760225 on 2017-11-2 at 00:38 ]
1<词>,2,3/段\,4{节},5(章)。
