When I build the below code, I get the following error.
While this code is a obviously a construed example, I have games where this error is actually occurring due to the build up of the memory use in the zp over a number of various valid functions.
sdcc -mmos6502 --opt-code-speed --max-allocs-per-node 10000 --data-loc 0x0001 --code-loc 0x4000 zptest.c -o zptest.ihx
?ASlink-Warning-Paged Area ZP Length Error
#include <stdint.h>
uint8_t buffer[64];
///< Hold three pointers and a 16 bit total live across a call to the next one
///< down, so the spill slots of the two cannot be overlaid
#define ZP_SPILL_FUNCTION( name, next ) \
uint8_t name( uint8_t *a, uint8_t *b, uint8_t *c, uint8_t i ) { \
\
uint8_t *p = a + i; \
uint8_t *q = b + i; \
uint8_t *r = c + i; \
uint16_t total = 0; \
\
while ( i-- ) { \
\
total += *p++; \
*q++ = total >> 8; \
*r++ = total; \
} \
\
return total + *p + *q + *r + next( a, b, c, 3 ); \
}
///< Ten of them at a time, named zp_spill_<ten>0 to zp_spill_<ten>9 and each
///< calling the one before it. "below" is what the first of the ten calls, so
///< passing the last of the previous ten carries the chain on across the groups
#define ZP_SPILL_DECADE( ten, below ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##0, below ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##1, zp_spill_##ten##0 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##2, zp_spill_##ten##1 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##3, zp_spill_##ten##2 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##4, zp_spill_##ten##3 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##5, zp_spill_##ten##4 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##6, zp_spill_##ten##5 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##7, zp_spill_##ten##6 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##8, zp_spill_##ten##7 ) \
ZP_SPILL_FUNCTION( zp_spill_##ten##9, zp_spill_##ten##8 )
///< Bottom of the chain, keeps nothing live
uint8_t zp_spill_leaf( uint8_t *a, uint8_t *b, uint8_t *c, uint8_t i ) {
(void)b;
(void)c;
return a[i];
}
ZP_SPILL_DECADE( 0, zp_spill_leaf )
ZP_SPILL_DECADE( 1, zp_spill_09 )
ZP_SPILL_DECADE( 2, zp_spill_19 )
ZP_SPILL_DECADE( 3, zp_spill_29 )
///< Enter at the top, so the whole chain is reachable and nothing is dropped
int main( void ) {
return zp_spill_39( buffer, buffer + 8, buffer + 16, 4 );
}
Looking at the map, REGTEMP starts at 169h, then fills from there. I'd like to request adding the variables filling the ZP before REGTEMP to the map file so it's possible to see what's causing the problem.
ZP 00000001 00000170 = 368. bytes (REL,CON,PAG)
Value Global Global Defined In Module
00000169 REGTEMP __sdcc_regs
00000169 ___SDCC_m6502_ret0 __sdcc_regs
....
$ sdcc -v
SDCC : z80/sm83/ez80/z80n/mos6502/mos65c02 4.6.2 #16701 (Linux)
I have no idea how why the spill locations do not show up in the linker map.
However, the asm file has the complete list of register spill locations in ZP (look for slocX_Y_Z).
You can also see which one each function uses in in the function header comments.
If you can't reduce the ZP spills, you can use --no-zp-spill option and the register spills will be placed at an absolute address instead of ZP.
The --no-zp-spill flag fixes the problem.
I'd be inclined to suggest to enable no-zp-spill as the default. I approve of speed optimisations, but in this case I would personally prefer that all my code compiles successfully without setting flags, even if it means it runs slightly slower. The code size is 16K, which is not huge. I'll enable this flag in my build system for everything from now on so it won't be an issue for me anymore.
It would be great if we could simply move some variables out of the spill area at link time, but I assume this isn't be possible since sdcc will need to make this decision at compile time.
The object files in the libraries do not hold the "debug" information the linker needs to show the spill locations and other local variables by name in the linker map file. It would be a nice extension if it could. I think it would require the .lst files alongside the .rel files in the libs.
I would vote against making --no-zp-spill the default. It generates less efficient code by default and when anyone bounces into this problem the solution to enable this option seems simple enough. Maybe it could use a better explanation in the manual then.
It could also be interesting to investigate if there really only is stuff in the zero page that belongs there and nothing else is eating into it.
My concern is that users won't think to go looking at compiler switches when they get an internal compiler error. It looks like a compiler bug. I think users will give up and go look for another compiler or wait for a future fix.
It never once occured to me there might be a compiler switch to fix this. I assumed it was a compiler bug.
I think it's better to be conservative and make sure the compiler always works by default. Make potentially breaking optimisation switches optional.
the spills are from the compiled code, not the library. It seems the reason why they don't show up in the map is because they are not declared as global.
There are 2 more things that end up in ZP:
1- 8 bytes used by the compiler for code generation (REGTEMP stack, DPTR, big return)
2- Overlay. Usually very small as it's the largest parameter size out of all leaf functions. There is some dead code to move this also out of ZP but the usefulness is very limited.
I agree, enabling --no-zp-spill by default does not seem a good idea.
The manual says:
--no-zp-spill Force the compiler to spill registers to 16-bit addressable memory (xdata) instead of Page Zero. When running out of Page Zero space, this option will allow to free many Page Zero locations at the expense of slightly larger and slower code
Any suggestions for improvement?
Last edit: Gabriele Gorla 2026-08-22
That is correct.
It is possible to implement enabling/disabling --no-zp-spill using #pragma to give the user finer control of how the spills are allocated.
The longer term fix is to implement fixed virtual registers in ZP to reduce the register pressure and limit the spills. This will also improve performance as the virtual registers will get register tracking (not possible on the spills)