Zero page

From llvm-mos

The default zero-page configurations for the llvm-mos-sdk are generally sufficient for pure C projects; the compiler can automatically make good use of the zero page available on a target. However, hand-written assembly may reserving zero page memory from the compiler. Mixed C and assembly programmers should acquaint themselves with the llvm-mos calling convention as well as our FAQs on inline assembly.

The llvm-mos code generator uses the zero page for two purposes: imaginary registers, and as a general store for variables and values. Both are controlled by the linker script, the former by setting symbol values, and the latter by assigning .zp.* sections created by the compiler to the zero page.

For the latter to work, the compiler needs to be made aware of how much contiguous zero page is available. This differs wildly from target to target. Additionally, zero page is very scarce, so the compiler can only safely make use of it if it can achieve a whole-program view of its usage.

Accordingly, zero page allocation is only enabled when compiling with link-time optimization (LTO). The amount available is set by the flag -mlto-zp=<num_bytes>, where num_bytes is the number of contiguous bytes of zero page available, not counting the imaginary registers. Each target in the SDK defaults to consuming the largest available contiguous region of the zero page, and the compiler will automatically make the best use of that region that it can.

User-written assembly routines may also place values in the zero page. Due to the limitations of the structure of a modern compiler, there's no way for the compiler to see into assembly files to extract the number of bytes used. Instead, the compiler must be told ahead of time the number of bytes used by passing the flag -mreserve-zp=<num_bytes>. This effectively decreases the number passed to -mlto-zp.

Alternatively, zero page locations can be reserved for assembly use by definining them in LTO-visible C using e.g. char __zp foo;or char __zeropage foo; . The compiler can see these, so it automatically deducts them from the available zero page; the resulting symbols can then be referenced by assembly. This is the approach used in the SDK, since zero page locations for unused routines can be optimized away by the compiler, and pointers to such variables can also be declared to be in the zero page, which allows them to fit in 8 bits. For example, char __zp *fptr = &foo; assert(sizeof(fptr) == 1); .

While the above can also be used to force a global to the zero page, this isn't recommended as a default practice. Without the annotation, the compiler may be able to optimize the variable away, perhaps by placing it in a processor register for its lifetime. Failing that, the compiler can already lift global values into the zero page automatically, and it does so based on a whole-program analysis of the loop structure of the program.