# String Pool Memory Allocation Changes (2025-11-23)

## Summary
Changed from malloc-based heap allocation to direct memory allocation from BSS_tail.

## OLD SYSTEM (REMOVED)
- String pool allocated via `malloc()` from z88dk heap
- Compiled with `-DAMALLOC` flag to enable heap
- `mb25_init(pool_size)` called malloc internally
- `mb25_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_tail` to `SP - stack_reserve`
- No `-DAMALLOC` flag (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
1. **File I/O doesn't use heap** - FCBs are static arrays in BSS
2. **z88dk heap adds overhead** - both code size and memory management
3. **Direct allocation is simpler** - just use the memory after BSS

## Files Changed
- `runtime/strings/mb25_string.c` - New init signature, no malloc/free
- `runtime/strings/mb25_string.h` - Updated function signature
- `src/codegen_backend.py` - Remove -DAMALLOC, generate pool calculation code

## Codegen Changes
Generated startup code now includes:
```c
/* 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()`
