Alan Weiner's information about
CodeWarrior for PalmPilot

 

January 1998
Note: The soon-to-be released CodeWarrior Release 4 will have a new pragma to permit overriding the compiler's deferred stack cleanup. I'll write more about it after R4 is released.

July 1998
Well, I lied - I never added anything after R4 was released...  Roadcoder's just updated the link to this page, so I'll write a little update now...

Metrowerks added the stack_cleanup pragma to handle this problem:

#pragma stack_cleanup on | off | reset (default: off)

Selecting this option will disable the deferred stack cleanup after
function calls. Usually this is not what you want because it
generates more and slower code.

Enabling this by putting
   #pragma stack_cleanup on
in your program causes the compiler to clean up the stack after every function call, rather than deferring it until later.  This eliminates the stack overflow problem, at the expense of slower execution (due to executing more instructions to do the more-frequent cleanup) and a larger program size (due to the additional instructions).

 

28 October 1997

Recently, while developing some software for the PalmPilot, I ran into an anomaly regarding how Metrowerks' CodeWarrior handles the stack.

 

click here to download a CodeWarrior project file (from CW for Windows) which demonstrates this anomaly.

 

Description of the problem:

When CodeWarrior compiles code for something like:

error = DmWrite(a, b, c, d);

It pushes the arguments onto the stack, and calls the function. The problem is that when the function returns, the arguments are still on the stack - this is normal C behavior.

CodeWarrior (like many other compilers) bunches up the stack-cleaning "pops" until later, to optimize code size (one stack adjustment instead of one per function call) and speed (one instruction instead of many). Both good goals, but there's a risk - which I ran into: if you do many function calls in a row, you could eat up the stack and crash.

In my case, I had a macro which built a word based on several parameters (they're bit-fields within the word) and then wrote that word into an already-opened record. As it turns out, it doesn't matter whether it's a macro or a function - the delayed-stack-adjustment appears either way.

If we did something like this:

// args to DmWrite are: (just as a reminder...) :)
// pointer to opened and memory-locked record
// offset within record at which to write data
// pointer to data to be written
// size of data to be written in bytes

DmWrite(pRecord, 0, &worddata, 2);
DmWrite(pRecord, 2, &worddata, 2);
DmWrite(pRecord, 4, &worddata, 2);
DmWrite(pRecord, 6, &worddata, 2);

agreed, this is a dumb thing to do, but it's a simplified version of what I actually did do - in my case, it's just a simple way to build a known database...

What gets compiled is:

push 2
push address of worddata
push 0
push value of pRecord
trap #15
dc.w <DmWrite>

then the whole sequence is repeated for each C line. (the "push 0" changes each time, of course)

Note that there is never an adjustment to A7 (or SP; same register...) -- later on there will be something like:

lea 40(sp), sp

(this clears off the 4 dwords pushed for each call, for 4 calls worth)

Another thing to note is that while the stack is preserved and restored when you call a function (the stack pointer is moved into A6 upon calling a function, and restored from A6 upon exiting) the upper-level call _to_ that function still has its arguments on the stack. In other words, if I call a function "DoTheWrite()" which takes those same arguments and then calls DmWrite(), the arguments for the call to DmWrite (from DoTheWrite) are cleaned off as DoTheWrite returns, but the arguments passed to DoTheWrite are still on the stack.

Now, why doesn't every program run out of stack space and crash? After all, we're all doing many calls to functions and OS APIs...

Well, the compiler keeps track of what's going on - if you do something that could change the stack depth so it wouldn't be consistent later on (preventing a simple "lea xxx(sp),sp" from cleaning up the stack) then it cleans the stack prior to (or within) that "offensive" block.

the same code as above, but doing it as a loop:

for (i=0, i<=6; i+=2) {
    DmWrite(pRecord, i, &worddata, 2);
}

In this case, the compiler believes that it won't know the stack after the for-loop completes, so it cleans it up in each loop iteration. The same thing may happen in a conditional, or in other cases.

Since most functions aren't simply long sequences of calls, the stack is cleaned up often enough that it's not a problem.

Note that it *could* be though! What if you had a case like the above deep-deep inside your code - A calls B which calls C which eventually gets to some routine that does a whole bunch of linear calls - so if you call that function at the right time (well, wrong time - when you're stack is almost used up) you'll crash! Even though you've executed it in other cases without problem.

I've tried this with all optimizations off; that doesn't help - CW still does the delayed stack cleanup. John Lehett checked GCC; with optimizations on, GCC does the same delayed cleanup. With optimizations off, GCC cleans up the stack after each function call returns.