User Stack Allocation

The 56800E compilers build frames for hierarchies of function calls using the stack pointer register (SP) to locate the next available free X memory location in which to locate a function call’s frame information. There is usually no explicit frame pointer register. Normally, the size of a frame is fixed at compile time. The total amount of stack space required for incoming arguments, local variables, function return information, register save locations (including those in pragma interrupt functions) is calculated and the stack frame is allocated at the beginning of a function call.

Sometimes, you may need to modify the SP at runtime to allocate temporary local storage using inline assembly calls. This invalidates all the stack frame offsets from the SP used to access local variables, arguments on the stack, etc. With the User Stack Allocation feature, you can use inline assembly instructions (with some restrictions) to modify the SP while maintaining accurate local variable, compiler temps, and argument offsets, that is these variables can still be accessed since the compiler knows you have modified the stack pointer.

The User Stack Allocation feature is enabled with the #pragma check_inline_sp_effects [on|off|reset] pragma setting. The pragma may be set on individual functions. By default the pragma is off at the beginning of compilation of each file in a project.

The User Stack Allocation feature allows you to simply add inline assembly modification of the SP anywhere in the function. The restrictions are straight-forward:
  1. The SP must be modified by the same amount on all paths leading to a control flow merge point.
  2. The SP must be modified by a literal constant amount. That is, address modes such as “(SP)+N” and direct writes to SP are not handled.
  3. The SP must remain properly aligned.
  4. You must not overwrite the compiler’s stack allocation by decreasing the SP into the compiler allocated stack space.

Point 1 above is required when you think about an if-then-else type statement. If one branch of a decision point modifies the SP one way and the other branch modifies SP another way, then the value of the SP is run-time dependent, and the compiler is unable to determine where stack-based variables are located at run-time. To prevent this from happening, the User Stack Allocation feature traverses the control flow graph, recording the inline assembly SP modifications through all program paths. It then checks all control flow merge points to make sure that the SP has been modified consistently in each branch converging on the merge point. If not, a warning is emitted citing the inconsistency.

Once the compiler determined that inline SP modifications are consistent in the control flow graph, the SP’s offsets used to reference local variables, function arguments, or temps are fixed up with knowledge of inline assembly modifications of the SP. You may freely allocate local stack storage:
  1. As long as it is equally modified along all branches leading to a control flow merge point.
  2. The SP is properly aligned. The SP must be modified by an amount the compiler can determine at compile time.

A single new pragma is defined. #pragma check_inline_sp_effects [on|off|reset] will generate a warning if the user specifies an inline assembly instruction which modifies the SP by a run-time dependent amount. If the pragma is not specified, then stack offsets used to access stack-based variables will be incorrect. It is the user’s responsibility to enable #pragma check_inline_sp_effects, if they desire to modify the SP with inline assembly and access local stack-based variables. This pragma has no effect in function level assembly functions or separate assembly only source files (.asm files).

In general, inline assembly may be used to create arbitrary flow graphs and not all can be detected by the compiler.

For example:

REP #3

ADDA #2,SP

This example would modify the SP by three, but the compiler would only see a modification of one. Other cases such as these might be created by the user using inline jumps or branches. These are dangerous constructs and are not detected by the compiler.

In cases where the SP is modified by a run-time dependent amount, a warning is issued.

Listing: Example 1 - Legal Modification of SP Using Inline Assembly
#define EnterCritical() { asm(adda #2,SP);\

                        asm(move.l  SR,X:(SP)+); \ 

                        asm(bfset  #0x0300,SR); \ 

                        asm(nop); \

                        asm(nop);}



#define ExitCritical()  { asm(deca.l SP);\ 

                           asm(move.l x:(SP)-,SR); \  

                           asm(nop);\

                        asm(nop);}



#pragma check_inline_sp_effects on



int func()

{

         int a=1, b=1, c;



         EnterCritical();



         c = a+b;



         ExitCritical();



}

This case will work because there are no control flow merge points. SP is modified consistently along all paths from the beginning to the end of the function and is properly aligned.

Listing: Example 2 - Illegal Modification of SP using Inline Assembly
#define EnterCritical() { asm(adda #2,SP);\

                        asm(move.l  SR,X:(SP)+); \ 

                        asm(bfset  #0x0300,SR); \ 

                        asm(nop); \

                        asm(nop);}



#define ExitCritical()  { asm(deca.l SP);\ 

                          asm(move.l x:(SP)-,SR); \ 

                          asm(nop);\ 

                        asm(nop);}



#pragma check_inline_sp_effects on



int func()

{

          int a=1, b=1, c;



          if (a) 

{

                 EnterCritical();



                 c = a+b;



          }

          else {

                 c = b++;

          }



          ExitCritical();



          return (b+c);

}

This example will generate the following warning because the SP entering the "ExitCritical" macro is different depending on which branch is taken in the if. Therefore, accesses to variables a, b, or c may not be correct.

Warning : Inconsistent inline assembly modification of SP in this function.

M56800E_main.c line 29    ExitCritical();
Listing: Example 3 - Modification of SP by a Run-time Dependent Amount
#define EnterCritical() { asm(adda R0,SP);\

                          asm(move,l  SR,X:(SP)+); \

                          asm(bfset   #0x0300,SR); \

                          asm(nop); \

                          asm(nop);}



#define ExitCritical()  { asm(deca.l SP);\

                          asm(move.l X:(SP)-,SR); \

                          asm(nop);\

                          asm(nop);}



#pragma check_inline_sp_effects on

int func()

{

          int a=1, b=1, c;



          if (a) 

          {

                  EnterCritical();



                  c = a+b;



          }

          else {

                  EnterCritical();

                  c = b++;

          }







          return (b+c);

}

This example will generate the following warning:

Warning : Cannot determine SP modification value at compile time

M56800E_main.c line 20     EnterCritical();

This example is not legal since the SP is modified by run-time dependent amount.

If all inline assembly modifications to the SP along all branches are equal approaching the exit of a function, it is not necessary to explicitly deallocate the increased stack space. The compiler "cleans up" the extra inline assembly stack allocation automatically at the end of the function.

Listing: Example 4 - Automatic Deallocation of Inline Assembly Stack Allocation
#pragma check_inline_sp_effects on

int func()

{

           int a=1, b=1, c;



           if (a) 

           {

                     EnterCritical();



                     c = a+b;



           }

           else {

                     EnterCritical();

                     c = b++;

           }







           return (b+c);

}

This example does not need to call the "ExitCritical" macro because the compiler will automatically clean up the extra inline assembly stack allocation.