The project with my “beloved” Pic18 is nearly over, the one I’m starting is based on a Freescale ARM with enough horse-power to run a modest desktop. Nonetheless an interface (to the proprietary field bus) is still. . . You guessed, a glorious PIC 18. This is one of the reasons I keeping an eye on the Microchip tools strategy for these chips. The other one is that the nearly over project is likely to be maintained in the coming years to implement small changes and requests from the customer. So, what happened in their yard since the last time I wrote?
First, mplab has been driven into retirement and replaced by a customization of Netbeans, named Mplab X. This I consider a good move since I much prefer this IDE over the sadly ubiquitous Eclipse. The reason is long to tell and may be partly invalidated by the latest progress, but that’s enough matter for another post.
Mplab X is still young and that may be the reason for some clumsiness in migration from Mplab 8.
The other strategic move that Microchip is doing is the unification of the two families of compiler. Microchip had the low cost, under-performing mcc18, then re-branded as mplabc18, and the acquired, high cost, high performance HTC.
Microchip decided to dump mplabc18 and to add some compatibility into the HTC so that mplabc18 code could be smoothly recompiled. This new version has been called xc.
Given what I witnessed using mplabc18 for over 3 years, I thought this had to be a good idea.
Then I took a look at the xc8 manual and I discovered that the compiler is not capable of generating re-entrant code. Despite of this Microchip claims that xc is ANSI (89) compliant. I could rant on the fact that c89 is no longer the c standard at least since 12 years ago, but I will concentrate on re-entrant code (or lack of thereof).
A function is re-entrant when it is computed correctly even if it is called one or more times before returning.
The first case is the recursive algorithm. Many algorithms on trees are formulated quite intuitively in a recursive form. Also expression parsing is inherently recursive.
Do you need recursion on a PIC18? May be not, also because – as you may remember – the little devil has a hardware call stack of only 31 entries.
Is there any other case you need re-entrancy? Yes pre-emptive multi-tasking – a task executing a function may be pre-empted and another task be switched in, if you are unlucky enough then it may be entering the same function (in multiprogramming unluckiness is the norm).
You may correctly object that multitasking on an 8 bit is overkilling. I agree, even if I don’t like to give up options before starting to analyze the project requirements.
Anyway there is a form of multitasking you can hardly escape even on 8bits – interrupts.
Interrupts occur every time the hardware feels it right to do so, or – more precisely – asynchronously. And it is perfectly legal and even desirable to have some library code shared between the interrupt and non-interrupt contexts.
When I pointed this, Microchip technical support answered that this case is solved through a technique they call “function duplication” (see section 5.9.5 in the compiler manual). In fact if you don’t use a stack for passing parameters to a function you may use an activation record (i.e. the parameters and the space for return value) in a fixed position of the static ram. Of course this causes your code not to be re-entrant (that’s why you usually want a stack). But you can simply provide a two levels re-entrancy by duplicating the activation record and the right one according to the context you run in.
Note that you don’t duplicate the code, just the activation records.
Neat trick, at least until you realize that a) PIC18 has two interrupts levels, so you would need three copies of activation records and b) this is going to take a lot of space.
In this sense the call stack is quite efficient because it keeps only what is needed in the current execution point. You will not find, in the stack, anything that does not belong to the dynamic chain of function invocations. Instead, if you pre-allocate activation records and local variables you need that space always.
Well this may not be completely true since the compiler can actually perform convoluted static analysis and discover which functions are mutually exclusive. Say that function a() calls first b() and then c() and that b() and c() are never called elsewhere and that their address is never taken. In this case you may reuse the same memory space for activation records and locals first to host b() stuff and the to host c() stuff.
Note that this kind of analysis is very delicate and sometimes not even possible. In fact you may have function pointers, but you may also have assembly code calling at fixed address. So I don’t think this kind of optimization can be very effective, but I would need to run some tests to support my claim.
Let’s get back to my firmwares. The most complex one counts about 1000 functions. Nearly 700 of them require one or more parameters.
Almost every one of them use local variables.
Even if we count 1 byte per function (and I am underestimating) we are talking of 2k on a micro that has 3.5k of ram.
Yes, I have to admit that the static approach is very fast because addressing is fixed. In any point you know the address of the variable at compile time. You don’t have to walk the stack and direct access is fast.
Anyway what I wrote is enough to demonstrate that it is not possible to port trivially a complex code from mplabc18 to xc8.
A last case when you need re-entrancy is when you have several function wrappers that accepts function pointers. If the wrapped code calls another code that uses the same wrapper you are again in the need for re-entrancy.