String Pool Memory Allocation Changes (2025-11-23)¶
Summary¶
Changed from malloc-based heap allocation to direct memory allocation from BSS_tail.
Toolchain note¶
The scheme itself is compiler-independent — the pool always occupies the gap between the end of BSS and the stack. What differs is how the program obtains those two addresses:
- uc80 (preferred, with um80/ul80): it has no inline assembly, and C cannot name the
linker symbols directly (
extern char __BSS_END[]emitsEXTRN ___BSS_ENDand fails to link). Both the BSS end and the stack pointer therefore come from the assembly shimruntime/strings/mb25_uc80_shim.mac, which is assembled separately withum80and linked in as its own.rel. - z88dk (alternate): supplies
__BSS_tailand accepts a__nakedfunction with inline__asm__. That is the form shown below, because z88dk was the only toolchain in use when this change was made.
See TOOLCHAIN_POLICY.md for the full comparison.
OLD SYSTEM (REMOVED)¶
- String pool allocated via
malloc()from z88dk heap - Compiled with
-DAMALLOCflag to enable heap mb25_init(pool_size)called malloc internallymb25_cleanup()called free()- Fixed pool size (default 2048 bytes, configurable)
- Max usable: ~39KB due to heap management overhead
- Extra ~1KB code for heap allocator
NEW SYSTEM¶
- String pool uses memory directly from
__BSS_tailtoSP - stack_reserve - No
-DAMALLOCflag (no heap linked in) mb25_init(pool_address, pool_size)receives pre-calculated values- No malloc/free needed
- Dynamic pool size based on available memory
- Max usable: ~56KB on 64K CP/M system
- Smaller binary (no heap code)
Memory Layout¶
0x0100 ┌─────────────────────┐ TPA Start
│ Program Code │
│ Static Variables │
│ FCBs (file I/O) │ ← File buffers in BSS, not heap!
├─────────────────────┤ __BSS_tail
│ │
│ STRING POOL │ ← All free memory for strings
│ (dynamic size) │
│ │
├─────────────────────┤ SP - 1024
│ Stack reserve │
└─────────────────────┘ SP
Key Findings¶
- File I/O doesn't use heap - FCBs are static arrays in BSS
- z88dk heap adds overhead - both code size and memory management
- Direct allocation is simpler - just use the memory after BSS
Files Changed¶
runtime/strings/mb25_string.c- New init signature, no malloc/freeruntime/strings/mb25_string.h- Updated function signaturesrc/codegen_backend.py- Remove -DAMALLOC, generate pool calculation code
Codegen Changes¶
Generated startup code now includes (z88dk form; under uc80 the same two values arrive
through the .mac shim described in the toolchain note above):
/* Get BSS end and SP for pool calculation */
extern unsigned char __BSS_tail;
static uint16_t _get_sp(void) __naked {
__asm__("ld hl,0\n add hl,sp\n ret");
}
int main(void) {
uint16_t pool_start = (uint16_t)&__BSS_tail;
uint16_t pool_size = _get_sp() - 1024 - pool_start;
mb25_init((uint8_t*)pool_start, pool_size);
...
}
Additional: Shell Sort Replaces qsort¶
The garbage collector now uses inline shell sort instead of stdlib's qsort:
- Avoids linking qsort from stdlib (no stdlib.h needed)
- Shell sort is efficient for small arrays (typically <100 strings)
- Same O(n log n) complexity as qsort
- Implemented in sort_by_data_ptr() and sort_by_str_id()