|
We now apply these knowledge to implement the flow control of a non-leaf subroutine
We start with the implementation of the leaf subroutine:
leafMethod:
// reg lr = return addr
....
....
.... (instructions in the method)
/* -------------------------------------------
End of method, we need to return to caller
------------------------------------------- */
mov pc, lr
|
We first add a function call to make it a non-leaf function....
The bl someFunction instruction will overwrite the return address in the lr register
nonLeafMethod:
// reg lr = return addr --- overwritten
....
bl someFunction // overwrites LR
....
.... (instructions in the method)
/* -------------------------------------------
End of method, we need to return to caller
------------------------------------------- */
// LR contains incorrect return address
mov pc, lr
|
Recall the solution: the function must save the return address in LR on the runtime stack
We save the return address in register LR on the stack as soon as possible:
nonLeafMethod:
push {lr} // Save return address on stack
....
bl someFunction // overwrites LR
....
.... (instructions in the method)
/* -------------------------------------------
End of method, we need to return to caller
------------------------------------------- */
// LR contains incorrect return address
mov pc, lr <---- use an incorrect return address
|
Observe that: LR contains an incorrect return address when the function returns !
We must first restore the return address from the stack with pop {lr}:
nonLeafMethod:
push {lr} // Save return address on stack
....
bl someFunction // overwrites LR
....
.... (instructions in the method)
/* -------------------------------------------
End of method, we need to return to caller
------------------------------------------- */
pop {lr} // Restore the correct return address
mov pc, lr
|
This is the structure of a non-leaf function/method !!!
Consider the structure of a non-leaf function:
nonLeafMethod:
push {lr} // Save return address on stack
....
bl someFunction // overwrites LR
....
.... (instructions in the method)
/* -------------------------------------------
End of method, we need to return to caller
------------------------------------------- */
pop {lr} // Restore the correct return address
mov pc, lr
|
Question: can you replace these 2 assembler instructions with one instruction ???
We pop the return address directly into the program counter (PC):
nonLeafMethod:
push {lr} // Save return address on stack
....
bl someFunction // overwrites LR
....
.... (instructions in the method)
/* -------------------------------------------
End of method, we need to return to caller
------------------------------------------- */
pop {pc} // Return to caller function
|
Example: (expressed in Java)
public static void main(String[] args)
{
A( );
}
public static void A( )
{
...
B( ); // Calls B( )
...
}
public static void B( )
{
... // Does not call any method/function
...
}
|
How to implement the call sequence in ARM assembler:
mov r0, #1111
mov r1, #1111
bl A
mov r2, #2222
mov r3, #2222
bl A
mov r4, #3333
mov r5, #3333
Stop:
A:
push {lr} // Save return address
mov r0, #7777
mov r1, #7777
bl B // Overwrites lr, it's OK, we saved it !!!
mov r2, #8888
mov r3, #8888
pop {pc} // A returns to main !!!
B:
mov r0, #9999
mov r1, #9999
mov pc, lr // B can return to A
|
DEMO: /home/cs255001/demo/asm/8-sub/bl+pop.s
Consider a deeper nesting of function calls:
main: // main calls A, A calls B, B calls C, C calls D
mov r0, #1111
bl A
mov r0, #2222
bl A
mov r0, #3333
Stop:
nop
A:
push {lr} // Save return address
mov r0, #4444
bl B // Overwrites lr, it's OK, we saved it !!!
mov r0, #5555
pop {pc} // A returns to main !!!
B:
push {lr} // Save return address
mov r0, #6666
bl C // Overwrites lr, it's OK, we saved it !!!
mov r0, #7777
pop {pc} // B returns to A !!!
C:
push {lr} // Save return address
mov r0, #8888
bl D // Overwrites lr, it's OK, we saved it !!!
mov r0, #9999
pop {pc} // C returns to B !!!
D:
mov r0, #1234
mov pc, lr // D returns to C
|
DEMO: /home/cs255001/demo/asm/8-sub/bl+pop2.s
Situation: main( ) starting....
Content of the runtime stack:
Situation: main( ) called A( ) and A( ) has saved its return address:
Content of the runtime stack: contain the return address to main
Situation: A( ) called B( ) and B( ) has saved its return address:
Content of the runtime stack: contain the return address to A and to main
Situation: B( ) called C( ) and C( ) has saved its return address:
Content of the runtime stack: contain the return address to B, to A and to main
Situation: right before the function C( ) returns (to B( )):
Content of the runtime stack: contain the return address to B, to A and to main
Situation: right after the function C( ) has returned to B( ):
Content of the runtime stack: contain the return address to main and to A
Situation: right before the function B( ) returns (to A( )):
Content of the runtime stack: contain the return address to main and to A
Situation: right after the function B( ) has returned to A( ):
Content of the runtime stack: contain the return address to main
Situation: right before the function A( ) returns (to main( )):
Content of the runtime stack: contain the return address to main
Situation: right after the function A( ) has returned to main( ):
Content of the runtime stack: empty !!!
|