Javi Perez wrote:I'm making several changes like using more global variables, reducing the parameter list in functions, using unsigned data types, etc.
It's very easy to make the transition to global variables without making significant changes to the program:
Code: Select all
int function(int a, int b)
{
int i, j, k;
struct sp1_ss *s;
...
}
becomes:
Code: Select all
int function(int a, int b)
{
static int i, j, k;
static struct sp1_ss *s;
...
}
Instead of being allocated on the stack at runtime, the compiler will allocate those variables globally but give them complicated names so that they don't collide with other variables of the same C name. Even though static memory will be allocated for each such variable, this will always save memory because the code generated to access global variables is much cheaper than the code generated to access local variables. (The one exception might be the last local variable declared in a function; with sccz80 compiling it may be able to access it with pairs of pop/push functions which is two bytes rather than 3-byte static loads like "ld hl,(...)").
Unsigned types should always be used too. The Z80 code to deal with signed comparisons is clumsier than unsigned and having variables signed means the compiler must insert sign extension code when casting to larger bit widths. C contains integer promotion rules which means smaller types like char will be cast to integer before many operations and that will involve sign extension code for signed char.
If you have big functions with long parameter lists you can save code size by copying some parameters into static locals before using them:
Code: Select all
int function(int a, int b, int c, int d, int e, int f, int g)
{
...
}
becomes:
Code: Select all
int function(int a, int b, int c, int d, int e, int f, int g)
{
static int la, lb, lc, ld, le, lf, lg;
la = a; lb = b; lc = c; ld = d; le = e; lf = f; lg = g;
// use la..lg instead of a..g here
...
}
If you're doing something like that you'll want to change frequently used parameters to static locals and probably leave alone one-off used parameters since in this case the extra assignment plus one access might be more expensive than a single local parameter lookup. A little experimentation will reveal what's best.
After this there is the idea of overlays but this requires some planning and clear thinking. You can create a global overlay struct and have storage space inside it that is shared by a lot of functions. But if you do that you have to make sure functions don't use the same variable space at the same time. This requires some planning from the outset so I wouldn't recommend retrofitting an existing program unless you do a rewrite.
Unfortunately, building the program to start in the contended area, for example at 24000, does not help as the program won't simply start.
Yes basic needs some space to actually load the program into memory. But what you can do is use basic to load the main program and then do a tape load from C to load data at 23296.
http://www.z88dk.org/wiki/doku.php?id=l ... m#tape_i_o tape_load_block() is just a wrapper that jumps into the ROM to load a block of memory. You can do a headerless load and appmake is able to create a headerless tape block which you can just append to your tap file.
To do this you will have to place some of your global data at address 23296. One of the reasons z80asm was updated was to make this very easy to do. With the new clib you would just create an asm file like this:
lowmem.asm
Code: Select all
SECTION lowmem
org 23296
PUBLIC _a, _b
_a: defs 100
_b: defw 2
In your C program you can place variables there and use them as normal.
Code: Select all
extern unsigned char a[100];
extern int b;
When you compile, you include this file on the compile line:
zcc +zx -vn -clib=new test.c lowmem.asm -o test
The output will be two binaries:
test_CODE.bin
test_lowmem.bin
The first one is your program and the second one is the "lowmem" section which is your data at org 23296. You would create a tap file with test_lowmem.bin appended to the end and have your C program load it from tape first thing.
The classic library is not using sections but I think you can still do this by enabling sections on the command line! Give it a try:
zcc +zx -vn test.c lowmem.asm -o test -lndos -lsp1 -z80asm-sections
The output should be two binaries:
test.bin
test_lowmem.bin
The reason why this would work is there is no memory map so all stuff gets placed into the no-name section (hence "test.bin" rather than "test_CODE.bin" as in the new compile everything is being bundled into the CODE section). Except, of course, for the one defined section so it is output as a separate binary.
The other way to place stuff is to use the special @ syntax of sccz80 to put variables individually at specific memory locations. This can be tedious and error-prone however.
As a last option, I'll follow the suggestion to use the old splib routines instead of sp1.
I'm not so sure splib will always be smaller. Under splib sprite definitions are larger because the last column of sprites has to be an actual blank line whereas under sp1 the column can be empty. sp1 also separates the sprite draw code into individual parts whereas splib forces you to include all the sprite type draw code in each compile (you get draw code for OR, XOR, LOAD, MASK types whether you use them or not). The data structures are smaller but I'm not sure if it's a clear win in terms of memory use. It wouldn't hurt to try.
I'd also encourage you to try a new clib compile which I think will be at least a little bit smaller but also an sdcc compile which can sometimes be smaller as well.