[ snip ]
Niklas Holsti wrote:
That is interesting info on gcc, and new to me (I don't follow gcc development that closely). Thanks, David, this is definitely something one needs to know about (future) gcc.
Yes indeed, at least for the purpose of ensuring a standard calling sequence is used for a given function.
And the OP specifically asked about the IAR MSP430 compiler, anyway.
I was afraid that would be the answer, as far as it goes. Yuck.
There are several things under discussion here:
- Whether it makes sense to use a routine Spin, containing a loop (whether conditional or unconditional) as the last thing a thread should call in its time-slice, such that threads are always suspended and resumed only at this point, that is, within the call of Spin.
David, I think you have more or less agreed that this is a workable design for a non-preemptive (in my definition) time-sliced system that does not schedule other threads to use the slack left over in one thread's time-slice. I won't say more to defend it at this point.
- How difficult or time-consuming is it for an interrupt handler that interrupts the loop in Spin to find the return address of that call of Spin?
Assuming that
o the thread calls Spin using the normal calling sequence, in which the return address is left on top of the stack,
o the code in Spin does not push more data on the stack, and
o the handler is written in MSP430 assembly language,
then this is just one POP.W instruction, executed after the interrupt handler has popped the saved status register and saved interrupt-point PC from the stack. (I'm not very familiar with the MSP430 instruction set and its interrupt handling, so this may be a bit optimistic. But the MSP430 instruction set is claimed to be strong on stack accesses, so it should not be much harder.)
Thus, getting the return address of Spin (under the above assumptions) is quick and well-defined.
In fact, the tick interrupt handler could do it smartly as follows:
1) Pop the saved status register. 2) Pop the saved interrupt-point PC, check that it points to the loop in Spin, and then discard it. 3) Push back the saved status register.This makes the two top words on the stack be the resumption PC (the return address for the Spin call) and the saved status register, exactly the state needed for a future RETI to resume this thread. It is not even necessary to get and manipulate the return address for the Spin call. (I'm assuming that each thread has its own stack area.)
- Whether the Spin routine can or should be written in C.
If the C compiler generates code for Spin and for the calls to Spin that satisfies the above assumptions, it can be written in C. But David is right to say that it is hard to be sure that the assumptions do hold, and will continue to hold, if Spin is written in C. So let's assume that we write Spin in assembly language, which lets us be sure that the assumptions hold.
- Whether the thread-resumption latency can be shorter if the Spin loop is unconditional, and the return address of Spin is saved and used as the resumption point (case A), compared to the latency when the Spin loop polls a flag, the address in the interrupted loop is saved and used as the resumption point, and the interrupt handler sets the flag to make the loop terminate (case B).
I comment on that below.
Why would the kernel need that information, if it is not going to schedule another thread for the rest of this time-slice?
I don't see much fragility in it. It is beautifully simple: if the thread finished what it had to do, it is in Spin; otherwise not. (I hope I am lauding the OP here, not my own guess about the design.)
And perhaps the OP's kernel actually has an "I_am_done" kernel call, which just ends up in Spin.
See above: a POP instruction. It is safe and reliable, if Spin is written in assembly language.
Getting the address of R takes one POP in case (A). Probably faster than these 3-4 instructions, at least not much slower.
No, it is based only on putting the right code in Spin, and ensuring that Spin is called with the standard calling sequence that leaves a return address on top of stack. This is readily and normally done by writing Spin in assembly language. The compiler is not hobbled in the C code parts.
It can hardly overwhelm it if Spin contains just the loop -- not much to optimise there. Or do you mean to write the tick interrupt handler in C? I know that some C compilers claim that you can use them to write interrupt handlers, but to me this seems more fragile than writing Spin in C. Especially for an interrupt handler that is meant to switch threads, not just manage som peripheral device and return to the interrupted thread.
However, the OP said that Spin in the OP's kernel contains some other things, too, so it's hard to say what the optimiser could do.
What "overhead" are you talking about, David? If Spin's profile is as simple as the OP showed (void Spin (void)), inlining would directly save only one call or branch instruction per time-slice. Perhaps the optimiser could let the thread keep more local data in registers over the (in-lined) Spin call, avoiding some store/load instructions. The IAR MSP430 compiler defines R12-R15 as scratch registers (caller-save) and R4-R11 as preserved registers (callee-save), so an inlined function could increase the available registers from 8 to 12; hard to say if that would be significant.
If Spin is inlined we lose the ability to check for time-slice overruns by checking that the interrupted thread is in the unique and only Spin, so I think that Spin should not be inlined. Perhaps you consider this to be one of the "fragile" aspects of this overrun-checking method, but it is not difficult to make sure that Spin is not inlined.
Wow, this makes me feel like a preacher. But "raise your eyes to the text above", and believe! :-)
There can hardly be a more reliable aspect of a standard calling convention than the presence and location of the return address.
That sounds rather dogmatic. If it works and is reliable, why is it "wrong"? Too heretical?
Although I still think that case A is a bit faster (and feel I have given good reasons above and in my preceding posting) it isn't the main point in favour of case A, the unconditional loop. Given this design of a Spin function in which the threads are suspended and resumed, a flag-polling loop is logically unnecessary: after the tick interrupt, the thread that gets to poll the flag is *the* scheduled thread, so polling the flag is superfluous.
I think the design is a neat solution to specific, limited requirements. It is a bit tricky, but interrupt-handling and thread-switching are often tricky. I mean "tricky" in the sense of "a trick", not in the sense of "difficult".