[ell-i-developers] About ARM LR register and subroutine calls

  • From: Pekka Nikander <pekka.nikander@xxxxxx>
  • To: Ivan Raul <supra.material@xxxxxxxxx>
  • Date: Sat, 15 Feb 2014 10:56:48 +0200

[Moving a mail thread that started privately to the mailing list.  The 
discussion is about ARM subroutine calls, exceptions, and the LR register. ]

>> After reading, my only conclusion is that other instructions like BL or BLX 
>> change the LR value. Then, if an interrupt calls a subroutine, the BL or BLX 
>> instruction is used to enter the subroutine.
>> According to my logic, at subroutine entry the LR register should be pushed 
>> to the stack and at exit popped again. I assume that the only condition this 
>> could not happen is that the function has attribute naked. 

Well, the LR (link register) is pushed to the stack only if it may become 
dirty.  That is, if a subroutine doesn't call any other subroutines (and the 
compiler does know), then it doesn't push LR to the stack as you observed:

> Well, there are two options that subroutines can take:
> go_to_subroutine: bl / blx <entry>
> 1. entry:   push {..., lr};
>            // code of the function here
>            pop {..., pc};

In this case the code calls something else, or needs LR for something else, so 
that its value is not preserved during the subroutine run.  Hence, the value in 
LR is pushed to the stack in the beginning of the subroutine, and popped 
directly to the PC in the end of the subroutine, causing an immediate return to 
the address that was there in LR at the beginning.

> 2. entry:   // code of function here
>            bx lr;

In this case the subroutine does not call any other functions, so that the LR 
is preserved during its run.  Hence, there is no need to push it.

> In conclusion they write the lr value to the pc in some way. But that doesn't 
> imply that the original value of the lr before going to the subroutine is 
> restored. 

Right, the return address (or exception return code) in LR needs to end up in 
the PC at the end of the subroutine.  And you are right also with the latter, 
any previous return addresses must be in the stack or in some other external 
place, they won't get "magically" restored to LR.

Remember, ARM and THUMB to an even larger extend is a RISC.  Hence, the idea is 
to try to keep all instructions simple so that they can execute in one clock 
cycle (as most do).  If you had a traditional subroutine call, that would take 
a few clock cycles, for pushing the PC to the stack, advancing stack pointer, 
and copying the new execution address to the PC register.  Since we have a RISC 
here, instead of that a subroutine call is simply a simultaneous move to two 
registers: PC to LR and new address to PC.  That can be done in a single clock 
cycle (if the core has sufficient busses).  In a similar manner, in its 
simplest form the return is a simple move of the return address to the PC.  As 
you have seen, the return address typically comes from LR in a leaf function, 
again a simple move.  But it may come from any address.

>> My question to you is the following: Then why it is necessary to save the LR 
>> register in a variable for future comparison at exception entry? Is it not 
>> supposed that it will not change?
> Yes, it is necessary, in any case that the function calls any subroutine, the 
> lr value will change (a naive division can suffice). Actually, AFAIU even the 
> compiler can use the lr register if it needs it, as it can be used for 
> general purpose as r14.

Hmm.  I'm not sure if I understand what you mean.  The way LR is used is a 
compiler convention, defined in the ARM ABI, aka eabi.  (Cf. arm-nono-*eabi*-*) 
 In theory, the compiler may use LR in any way it wants, as long it is 
consistent.  In practise, for 99.9% of cases it is best to use it as specified 
in the ABI specification.  Hence, yes, the compiler may use LR as R14 it it 

> That is the reason why ChibiOS developers tell that if the system calls any 
> API function they need to save the original lr value. Even calling a simple 
> division can generate trouble. In that way, they may know if they are 
> returning to Tread or to Handler (another exception).

I'm not sure who are the ChibiOS developers you are referring to or what 
exactly they are saying.  In general, gcc and llvm handle LR according to the 
ABI specification, unless you declare a function naked, use certain pragmas, or 
do something similar.

> The approach you used is to make PendSV the last exception that will kick in 
> in case of tailchaining is a sure way returning to Thread mode after a 
> scheduling event.

Well, semantically yes but in practise no.  That is, if there is another 
exception taking place at the same priority level as PendSV while PendSV is 
executing (or any exception if PendSV keeps all exceptions disabled), in 
practise the core will do handle that (or any pending) exception(s) before 
returning to the Thread mode.  But that doesn't matter (I think I have some 
comments of that in the code), as semantically the situation is identical to 
that the core returns to the Thread mode, performs zero instructions, and 
enters Handler mode again.


Other related posts:

  • » [ell-i-developers] About ARM LR register and subroutine calls - Pekka Nikander