ln_arm_subroutines

advertisement
Subroutines
reasons for subroutines
− repeat same code, or similar code with slightly
different parameters
− hide design decisions or design complexity
− partition off code likely to change
− provide for separate compilation
− provide for reuse
Subroutines
"open" subroutine = macro
− resolved before assembly by textual substitution,
− body of routine is placed in-line at call site with
parameter substitution
"closed" subroutine = standard notion
− branch/execute/return at run time
− one copy of body which accesses its formal
parameters
Subroutines
Three main concepts involved:
• Transferring control from calling program to a
subroutine and back
• Passing parameter values to a subroutine and results
back from the subroutine
• Writing subroutine code that is independent of the
calling program
In addition, sometimes a subroutine must allocate space
for local variables.
Subroutines
Transferring control from calling program to a subroutine
and back
• Necessary to save the return address, then branch to
the subroutine.
• This can be accomplished in ARM using the branch and
link instruction bl
bl subr_name
• To transfer control back to the calling program, can use
the branch and exchange instruction bx
bx lr
Alternatively, you can pop the lr register to the pc
Subroutines
• Placing a return address in a link register works as long
as there are no nested subroutines.
• For nested subroutines, it is necessary to save the
return address to the stack
• subroutine calls and returns are LIFO, and older
processors typically save the return address of a caller
by pushing it onto a memory stack
• older processors typically pass parameters by pushing
them onto the same memory stack that holds the
return addresses
Subroutines
Parameters:
− actual parameters - values or addresses passed to
subroutine
− formal parameters - parameter names appearing in
subroutine
General structure of a subroutine
main:
@ call
…
…
bl subr
@ call subroutine
ret_addr: add r0, r1, r2
…
subr:
…
@ body of the subroutine
…
bx lr
@ return to caller
@ return
C functions
main()
What information must
{
compiler/programmer
int a,b,c;
keep track of?
...
c = sum(a,b); @ a,b,c:r0,r1,r2
}
/* really dumb sum function */
int sum(int x, int y)
{
What instructions can
return x+y;
accomplish the return?
}
Function Call Bookkeeping
• Registers play a major role in keeping track of information
for function calls
• Register conventions:
–Return address
lr
–Arguments
r0, r1, r2, r3
–Return value
r0, r1, r2, r3
–Local variables r4, r5, … , r12
• The stack is also used (more on this later)
Register Usage
Arguments into function
Result(s) from function
otherwise corruptible
(Additional parameters
passed on stack)
Register variables
Must be preserved
Scratch register
(corruptible)
Stack Pointer
Link Register
Program Counter
Register
r0
r1
r2
r3
r4
r5
r6
r7
r8
r9/sb
r10/sl
r11
- Stack base
- Stack limit if software stack checking selected
r12
r13/sp
r14/lr
r15/pc
- SP should always be 8-byte (2 word) aligned
- R14 can be used as a temporary once value stacked
Register Usage
• The compiler has a set of rules known as a
Procedure Call Standard that determines how to pass
parameters to a function (see AAPCS)
• CPSR flags may be corrupted by function call.
Assembler code which links with compiled code must
follow the AAPCS at external interfaces
• The AAPCS is part of the new Application Binary
Interface (ABI) for the ARM Architecture
Register Conventions
• CalleR: the calling function
• CalleE: the function being called
• When callee returns from executing, the caller needs to
know which registers may have changed and which are
guaranteed to be unchanged.
• Register Conventions: A set of generally accepted rules
as to which registers will be unchanged after a
procedure call (BL) and which may be changed.
Register Conventions
What do these conventions mean?
– If function R calls function E, then function R must
save any temporary registers that it may be using
onto the stack before making a BL call (caller
saved register values).
– Function E must save any saved registers it
intends to use before garbling up their values
(callee saved register values)
– Remember: caller/callee need to save only
volatile/saved registers they are using, not all
registers.
Saved Register Conventions
• r4-r11 (v1-v8): Restore if you change. Very important.
If the callee changes these in any way, it must restore
the original values before returning.
• sp: Restore if you change. The stack pointer must
point to the same place before and after the BL call,
or else the caller will not be able to restore values
from the stack.
Volatile Register Conventions
• lr: Can Change. The BX call itself will change this
register. Caller needs to save on stack if nested call.
• r0-r3 (a1-a4): Can change. These are volatile
argument registers. Caller needs to save if they’ll
need them after the call. e.g., r0 will change if there
is a return value
• r12 (ip) may be used by a linker as a scratch register
between a routine and any subroutine it calls. It can
also be used within a routine to hold intermediate
values between subroutine calls.
Rules for Subroutines
• Called with a BL instruction, returns with a
BX lr (or MOV pc, lr)
• Accepts up to 4 arguments in r0, r1, r2 and r3
• Return value is always in r0 (and if necessary in r1, r2, r3)
• Must follow register conventions (even in functions that only
you will call)!
What are the register conventions?
1) Save necessary values onto stack
2) Assign argument(s), if any
3) BL call
4) Restore values from stack
Instruction Support for Functions
BL subroutine_name (Branch-and-Link) is the instruction
to jump to a subroutine. It performs the following
operations:
– Step 1 (link): Save address of next instruction into lr
(Why next instruction? Why not current one?)
– Step 2 (branch): Branch to the given label
(subroutine name)
• BL always uses r14 to store the return address. r14 is
called the link register (lr)
Instruction Support for Functions
BX - performs a branch by copying the contents of a
general register, Rn, into the program counter, PC. The
branch causes a pipeline flush and refill from the address
specified by Rn.
• Instead of providing a label to jump to, the BX instruction
provides a register which contains an address to jump to
• Only useful if we know the exact address to jump
• Very useful for function calls:
– BL stores return address in register (lr)
– BX lr jumps back to that address
• Syntax for BX (branch and exchange):
BX register
Instruction Support for Functions
C
main()
{
...
sum(a,b);
...
}
// a,b:r4,r5
int sum(int x, int y)
{
return x + y;
}
A
R
M
address
1000 mov r0, r4
1004 mov r1, r5
1008 bl
sum
1012 ...
2000 sum: ADD r0, r0, r1
2004 BX lr
Note: returns to address 1012
@x=a
@y=b
@ lr = 1012 branch to sum
@ MOV pc, lr i.e., return
C functions
.text
.global main
.type main, %function
main:
push {lr}
mov r0, #37 @ put x in r0
mov r1, #55 @ put y in r1
bl sum
...
mov r0, #0
pop {pc}
.global sum
.type main, %function
sum:
push {lr}
add r0, r0, r1
pop {pc}
Nested Procedures
int sumSquare(int x, int y)
{
return mult(x,x)+ y;
}
• Some subroutine called sumSquare, and now sumSquare
is calling mult.
• There’s a value in lr that sumSquare wants to jump back
to, but this will be overwritten by the call to mult.
• Need to save sumSquare’s return address before the call
to mult.
Nested Procedures
• In general, it may be necessary to save some other
info in addition to lr.
• When a C program is run, there are 3 important
memory areas allocated:
– Static: Variables declared once per program, cease
to exist only after execution completes. e.g., C
globals
– Heap: Variables declared dynamically
– Stack: Space to be used by subroutines during
execution; this is where we can save register
values
Using the Stack
• We have a register sp which always points to the last
used space in the stack.
• To use the stack, we decrement this pointer by the
amount of space we need and then fill it with info.
• Consider the following C function:
int sumSquare(int x, int y)
{
return mult(x, x) + y;
}
Using the Stack
int sumSquare(int x, int y)
{
return mult(x, x)+ y;
}
sumSquare:
add sp,sp,#-8
"push" str lr, [sp,#4]
str r1, [sp]
mov r1, r0
@ mult(x,x)
@ call mult
ldr r1, [sp]
add r0,r0,r1
ldr lr, [sp, #4]
add sp,sp,#8
bx lr
@ restore y
bl mult
"pop"
mult: ...
@ space on stack
@ save ret addr
@ save y
@ mult()+y
@ get ret addr
@ restore stack
C functions
x86 example of swap routine in C
void main() {
void swap();
int a,b;
a = 5; b = 44;
swap(&a,&b);
}
main:
pushl %ebp
movl %esp,%ebp
!
!
!
subl $8,%esp
!
movl $5,-4(%ebp)
!
movl $44,-8(%ebp) !
leal -8(%ebp),%eax !
pushl %eax
!
leal -4(%ebp),%eax !
pushl %eax
!
call swap
!
addl $8,%esp
!
!
leave
ret
save old bp
set new bp as
current sp
sub for a and b
initialize a
initialize b
form addr of b
push onto stack
form addr of a
push onto stack
call
clean parms off
stack
C functions
x86 example output for swap routine in C (sp called esp, fp called ebp)
void swap(x,y)
int *x,*y;
{
int temp;
temp = *x;
*x = *y;
*y = temp;
return;
}
swap:
pushl %ebp
movl %esp,%ebp
subl $4,%esp
movl 8(%ebp),%eax
movl (%eax),%edx
movl %edx,-4(%ebp)
movl 8(%ebp),%eax
movl 12(%ebp),%edx
movl (%edx),%ecx
movl %ecx,(%eax)
movl 12(%ebp),%eax
movl -4(%ebp),%edx
movl %edx,(%eax)
leave
ret
!
!
!
!
!
!
!
!
!
!
!
!
!
save old bp
set new bp as current sp
sub for temp
move addr x into eax
indirectly get value in a
store into temp
move addr x into eax
move addr y into edx
indirectly get value in b
store indirectly into a
move addr y into eax
move temp into edx
store indirectly into b
C functions
ARM code for swap()
void swap(int *x, int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
return;
}
swap:
push {lr}
ldr r2, [r0]
ldr r3, [r1]
str r3, [r0]
str r2, [r1]
mov r0, #0
pop {pc}
@ addr x is in r0
@ addr y is in r1
@ save return address
@ move x into r2
@ move y into r3
@ store indirectly into x
@ store into y
Basic Structure of a Function
Prologue
entry_label:
add sp,sp, -framesize
str lr, [sp, #framesize-4]
@ save lr
@ save other regs if necessary
...
Body
(call other functions…)
Epilogue
restore other regs if necessary
ldr lr, [sp, framesize-4] @ restore lr
add sp,sp, #framesize
bx lr
Example
main() {
int x, y; /* x: r0, y: r1 */
...
m = mult(y,y);
...
}
int mult (int multiplicand, int multiplier)
{
int product;
product = 0;
while (multiplier > 0)
{
product += multiplicand;
multiplier -= 1;
}
return product;
}
Example
.global main
.type main, %function
x
.req r4
y
.req r5
prod .req r3
main:
push {r4, r5, lr}
@ save registers
mov x, #7 @ put x in r4
mov y, #5 @ put y in r5
/* set up registers for call to mult */
mov r0, x
mov r1, y
bl mult
@ call mult
mov r3,r0
@ m = mult(x, y)
done:
…
mov r0, #0
pop {r4, r5, pc}
Example
.global mult
.type mult, %function
mult:
push {lr}
product
.req r2
multiplicand .req r0
multiplier
.req r1
mov product, #0
cmp multiplier, #0
ble finished
loop:
add product, product, multiplicand
sub multiplier, multiplier, #1
cmp multiplier, #0
bgt loop
finished:
mov r0, product
pop {pc}
Passing Parameters to Subroutines in ARM
Parameters can be passed to subroutines in three ways:
• Through registers
• Through memory
• Via the stack
Passing Parameters in registers
• This is what we have done so far
• Fast way of transferring data
Passing Parameters by Reference
• Parameters can easily be stored in program memory
and then loaded as they are used
• Send the address of the data to the subroutine
• More efficient in terms of register usage for some
types of data, e.g. a long string of characters or an
array of int values
Passing Parameters on the stack
• Similar to passing parameters in memory
• The subroutine uses a dedicated register for a pointer
into memory – the stack pointer, register r13.
• Data is pushed onto the stack before the subroutine
call
• The subroutine gets the data off the stack.
• The results are then stored back onto the stack to be
retrieved by the calling routine.
Download