|
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 !!!
|