Saturday, September 05, 2009

Pascal-Style Local Functions in C (for Z8 Encore!)

When I was a kid, I used Turbo Pascal. It had one feature that I sorely miss in every embedded C compiler: local functions. Local, as in local to a block. Those become a necessity to maintain decent code performance and avoid the penalty of passing duplicate data via parameters. They also help maintain readable, decently factorized code.

A local function, were C to have it, would look like this:

void fun1(void) {
 int a;
 void fun2(void) {
   a = 0;
 }
}

I hope you see that fun2() is local to the main block of fun1(), and that the variable a is in scope within fun2().

A somewhat less clean, but equally well performing way of accomplishing this would be:

extern int a;
void fun2(void) {
  a = 0;
}
void fun1(void) {
...
  fun2();
}

Now the question remains: where do we actually define the variable a, which is supposed to be local to fun1?

It becomes easy if your compiler supports static frames. Static frames are, as their name implies, allocated statically by the linker, and are overlaid according to the call tree. With usual dynamic frames, automatic variables end up on the stack. It should not be any harder to do with dynamic frames, but I haven't checked it out yet.

ZDS II, the C IDE for Zilog's Z8 Encore! and ZNEO products, supports static frames at the assembler and linker level. A frame ends up defining a near and far segment; for a function called fun1 those segments are called ?_n_fun1 and ?_f_fun1, respectively.

Assuming our code is in a file named file.c, and we're compiling for large model, we get

// file.c
extern int a;

#pragma asm segment ?_f_fun1
#pragma asm _a ds 2
#pragma asm segment file_TEXT

void fun2(void) {
  a = 0;
}

void fun1(void) {
...
  fun2();
}

Here, we manually allocate storage for a in fun1's far frame (due to model being large). This method can be used to bring back the nifty (albeit buggy) feature of ZDS II 4.9.x, which got abandoned and is no more present in ZDS II 4.11.0: arbitrary far/near automatic variables.

The syntax used to look like this:

void fun(void) {
  near int a;
  far int b;
  ...
}

The near/far storage specification was ignored when using dynamic frames, but for static frames it allowed you selecting whether given automatic variable would be stored in near or far memory space. Accesses to far variables take an extra clock cycle per byte, and can bring an extra load penalty in some cases, where the data has to be transferred from far memory to a register using LDX, before being useable by the target opcode.

We implement this functionality as follows:

// file.c

#pragma asm segment ?_n_fun
#pragma asm _a ds 2
#pragma asm segment ?_f_fun
#pragma asm _b ds 2
#pragma asm segment file_TEXT

void fun(void) {
  extern near int a;
  extern far int b;
  ...
}

The main drawback of this method, besides it being prone to typos and suffering from relegating part of the compiler to the wrong side of the keyboard, is that the assembly-level symbols _a and _b really have file scope due to the fact that assembler sees it so. Thus, the canonical way of using this trick is to have a complete tree of nested functions all in one C source file, and having unique names for all the automatic variables that have to be accessed by the local functions.

We shoot two birds with one stone here: we regain the foreign-model automatic variables of ZDS II 4.9.x vintage, and we Pascal-style local functions, which can access the automatic variables present in the scope of the caller's call site. Naturally, this is a hack which should only be used where performance or storage limitations demand it. It could be implemented with an extra C preprocessor.

No comments:

Post a Comment