Topic : The troubles with NOAUTO

Forum : 8051

Original Post
Post Information Post
June 17, 2007 - 11:59pm
Guest

By default RC51 compiles in NOAUTO mode, meaning that local variables are allocated in shared static banks which are allocated to prevent sharing between procedures that are active simultaneously. The alternative is AUTO, where local variables are allocated on the stack, which on my uPSD3434 is a pretty small space.

The problem with int putchar( int)

If I supply my own version of putchar in order to use stdio routines (printf etc) with an LCD display, the compiler does not know that (say) main calls puts(char*) calls putchar( int) and so allocates the same pseudo-static memory for main's variables and those of putchar and its subroutines, resulting in main's locals becoming corrupted whenever it prints.

The only solution I can find is to ensure that my LCD driver declares no pseudo-static local variables, either by making all variables static and/or global or compiling everything EXCEPT the putchar entry point automatic and reentrant (see below). I can't declare putchar itself reentrant because its call is pre-compiled in the library and is of the pseudo-static type, so I have to ensure it will declare no local variables, whatever compiler switches I might later set.

The problem with interrupts and subroutines

A similar problem arises with interrupts. The interrupt service routine itself is implicitly automatic even under NOAUTO but any subroutine it calls, and any sub-subroutines need to be declared automatic (either compiled AUTO or declared reentrant) because the compiler does not recognise that their local variables must be kept in a space dedicated to that particular interrupt vector.

The problems with mixing AUTO and NOAUTO

If different parts of a program are compiled with different settings of the AUTO switch in an attempt to solve some of the above, further problems arise because the procedures use different parameter passing protocols, and the compiler does not detect how their targets were compiled. The internal (object file) names of procedures compiled with the reentrant keyword are marked so that they will be called correctly or will not link, but those simply compiled under AUTO are not, even though they generate the same code. And even if they were, global procedures would still have to be declared reentrant in the shared header file so that the compiler would know which call to generate.

If parameters are passed on local variables, either because there are too many parameters or because the NOAREGS switch is in force, then the calling procedure has to know where the local variables will be kept. This can only be done by either ensuring that all were compiled with the same setting of the AUTO switch, or that all automatic procedures with parameters that will be called from pseudo-static procedures are also declared reentrant. (As far as I know there is no way of calling a pseudo-static procedure with parameters from within an automatic one.)

Any thoughts or comments? Am I missing something? The implications seem to be quite restrictive on coding techniques for larger applications and shared code. It seems to me that the bank naming for pseudo-static variables needs to be a bit more clever to identify putchar etc. as a special case, and to separate out spaces for interrupt vectors. Or else these limitations need documenting.

Tim Jackson

Replies
Post Information Post
+1
0
-1
June 20, 2007 - 2:43pm
Guest

The linker should correctly parse the call tree so that your putchar()'s locals aren't overlaid with other vars. It's obviously not doing that. Are there any overlay-related linker warnings? Try disabling overlays for putchar() using #pragma NOOL. Also, if you haven't already sone so, read the map file. The overlay assignments are in there; you might get a clue.

Steven Pruzina

+1
0
-1
June 20, 2007 - 10:46pm
Guest

I eventually got it to work, mostly by dint of removing just about all local variables and parameters from the LCD module, and using globals instead. Dirty C but it works, and it's a small module. As a separate issue (tested separately) I was also struggling with getting the USB to execute commands outside the interrupt service routine, and so had a lot of messy stuff going on under interrupt; but that was just a typo, and that ISR is a lot less hazardous now too.

So I've worked around it, but it is not a very satisfactory solution, and when I get some time I'll revive the backup from before the fixes and try to pin down exactly what was going on.

The map listing happily showed both variables allocated to the same group and address, yes. One was in my "wait on LCD busy" routine and the other was in my "format flash disk" which reports progress on the screen via puts. There were no linker or compiler warnings. Maybe something about the declarations found a loophole in the linker.

I hadn't discovered NOOL, although the manual says it only works for BIT and IDATA, which wouldn't help for parameters, they are either DATA or XDATA depending on memory model. In fact my original collision was in DATA. I'll give it a try.

I've had a few similar incidents of overlay collisions with interrupt handlers in the past, but I hadn't used stdio in RIDE before.

BTW in my original post I typed NOAREGS when of course I meant NOREGPARMS. Also I think it is true to say register parameters are not used with external procedures either.

Another oddity, I found when debugging this (while trying stacked locals in AUTO mode) that if I looked at local variables in the calling routines down-stack, the debugger (watch/evaluate) gave bad values. It knew the variables were in-scope but didn't adjust the stack pointer to allow for calls in progress, and so showed (eg) the current routine's return address instead of the caller's top local. Once I stepped through the RET back into the calling routine, its variables came right again. 'Inspect' on the other hand thought they were out of scope.

Tim

+1
0
-1
June 21, 2007 - 4:42pm
Guest

Yes, I've reproduced the overlay fault, or one very like it. It was in fact in XDATA (I'm using the LARGE memory model, so that is the default). Linker has the variables in XGROUP01. format() is allocated 0Ch bytes at 08A9 for a struct of 4 chars, followed by an 8-byte array of int, which extends up to and including 08B4; and lcd_addr() is allocated 2 bytes at 08B4. So the allocations overlap by one byte. Format calls puts(), and putchar() calls lcd_addr to do things like line feed.

The call to puts() does print as expected, but trashes the format.

If I comment out the calls to puts() in format(), I get exactly the same memory allocations, so the call into stdio is not having any effect on the call tree as seen by linker. Increasing the array size in format() increases the overlap, and does not move lcd_addr()'s variables. (Now maybe if I can find something that does end at 8B3...)

Applying pragma NOOL did remove putchar's variables from the XGROUP, so that is another possible work-round. But it looks like there is a real bug there somewhere.

Tim