Add lib_mem_defrag and lib_mem_stats to memlib.c65 with demo

This commit is contained in:
Mattias Hansson 2026-05-16 02:11:51 +02:00
parent 9720d00dcb
commit a15d45a130
5 changed files with 340 additions and 5 deletions

View file

@ -127,13 +127,15 @@ All file operations must be restricted to the project's source code and document
- Test with verbose output: `go test -v ./...`
#### Rebuilding c65gm:
When making changes to the compiler, ask the user to rebuild c65gm:
When making changes to the compiler or library files, ask the user to rebuild c65gm using the build script:
```bash
go build -o c65gm
./build_c65gm.sh
```
This creates a fresh binary with your changes. The agent should:
The build script copies `lib/` into `internal/preproc/lib/` (for embedding into the binary) and then runs `go build -o c65gm`.
**IMPORTANT**: Library files (`lib/*.c65`) are embedded into the binary at build time. After modifying any `.c65` file in `lib/`, you MUST use `build_c65gm.sh` (not `go build` directly) to ensure the updated library is embedded. The agent should:
1. Make code changes
2. Ask the user to rebuild c65gm: `go build -o c65gm`
2. Ask the user to rebuild c65gm: `./build_c65gm.sh`
3. **Check file datetime**: Verify the c65gm binary was recently updated (use `ls -la c65gm` or similar)
4. Ask the user to run tests: `go test ./...`
5. Test the changes with example .c65 files

View file

@ -0,0 +1,3 @@
#!/bin/sh
PROGNAME="memlib_demo2"
c65gm ${PROGNAME}.c65

View file

@ -0,0 +1,197 @@
//-----------------------------------------------------------
// Memory Library Demo 2: Defrag & Stats
// Demonstrates heap defragmentation and statistics
//-----------------------------------------------------------
#PRAGMA _P_REMOVE_UNUSED 1
#INCLUDE <c64start.c65>
#INCLUDE <c64defs.c65>
#INCLUDE <memlib.c65>
#INCLUDE <cbmiolib.c65>
#PRAGMA _P_USE_CBM_STRINGS 1
GOTO start
WORD keep1
WORD keep2
WORD ptr_a
WORD ptr_b
WORD ptr_c
WORD ptr_d
WORD free_blocks
WORD total_free
WORD largest_free
//-----------------------------------------------------------
// Wait for key
//-----------------------------------------------------------
FUNC wait_key
BYTE key
WHILE 1
key = PEEK $c5
IF key = 64
BREAK
ENDIF
WEND
WHILE 1
key = PEEK $c5
IF key != 64
BREAK
ENDIF
WEND
POKE $c6 WITH 0
FEND
//-----------------------------------------------------------
// Print stats with label (hex output)
//-----------------------------------------------------------
FUNC print_stats
lib_cbmio_printlf("stats:")
lib_mem_stats(free_blocks, total_free, largest_free)
lib_cbmio_print(" blocks: $")
lib_cbmio_hexoutw(free_blocks)
lib_cbmio_lf()
lib_cbmio_print(" free: $")
lib_cbmio_hexoutw(total_free)
lib_cbmio_lf()
lib_cbmio_print(" biggest:$")
lib_cbmio_hexoutw(largest_free)
lib_cbmio_lf()
FEND
//-----------------------------------------------------------
// Main demo
//-----------------------------------------------------------
FUNC main
lib_cbmio_cls()
lib_cbmio_printlf("memlib demo 2: defrag")
lib_cbmio_printlf("======================")
lib_cbmio_lf()
lib_cbmio_print("initializing...")
lib_mem_init()
lib_cbmio_printlf("ready")
lib_cbmio_lf()
// Show initial state
lib_cbmio_printlf("after init:")
print_stats()
lib_cbmio_lf()
lib_cbmio_print("press any key...")
wait_key()
lib_cbmio_lf()
lib_cbmio_lf()
// Allocate 6 blocks.
// Keep keep1 and keep2 allocated to create three gaps:
// heap: [keep1][free][keep2][free][free]
lib_cbmio_printlf("allocating 6 blocks")
lib_mem_malloc(20, keep1)
lib_mem_malloc(30, ptr_a)
lib_mem_malloc(40, keep2)
lib_mem_malloc(50, ptr_b)
lib_mem_malloc(20, ptr_c)
lib_mem_malloc(60, ptr_d)
lib_cbmio_print("keep1: $")
lib_cbmio_hexoutw(keep1)
lib_cbmio_print(" ptr_a: $")
lib_cbmio_hexoutw(ptr_a)
lib_cbmio_lf()
lib_cbmio_print("keep2: $")
lib_cbmio_hexoutw(keep2)
lib_cbmio_print(" ptr_b: $")
lib_cbmio_hexoutw(ptr_b)
lib_cbmio_lf()
lib_cbmio_print("ptr_c: $")
lib_cbmio_hexoutw(ptr_c)
lib_cbmio_print(" ptr_d: $")
lib_cbmio_hexoutw(ptr_d)
lib_cbmio_lf()
lib_cbmio_lf()
print_stats()
lib_cbmio_lf()
lib_cbmio_print("press any key...")
wait_key()
lib_cbmio_lf()
lib_cbmio_lf()
// Free ptr_a and ptr_b (non-adjacent, between keep1 and keep2, and after keep2)
lib_cbmio_printlf("freeing ptr_a and ptr_b")
lib_mem_free(ptr_a)
lib_mem_free(ptr_b)
lib_cbmio_printlf("freed")
lib_cbmio_lf()
print_stats()
lib_cbmio_lf()
lib_cbmio_print("press any key...")
wait_key()
lib_cbmio_lf()
lib_cbmio_lf()
// Free ptr_c and ptr_d (adjacent to each other, after ptr_b's free block)
// Now gaps are: [keep1][gap_a][keep2][gap_b][gap_c]
// gap_a and gap_b are separated by keep2
// gap_b and gap_c are adjacent -> should merge after freeing gap_c
lib_cbmio_printlf("freeing ptr_c and ptr_d")
lib_mem_free(ptr_c)
lib_mem_free(ptr_d)
lib_cbmio_printlf("freed")
lib_cbmio_lf()
// Show fragmented state (keep1 and keep2 still allocated)
lib_cbmio_printlf("before defrag (keep1,keep2 allocated):")
print_stats()
lib_cbmio_lf()
lib_cbmio_print("press any key...")
wait_key()
lib_cbmio_lf()
lib_cbmio_lf()
// Defrag!
lib_cbmio_print("running defrag...")
lib_mem_defrag()
lib_cbmio_printlf("done")
lib_cbmio_lf()
// Show defragged state:
// After defrag: [keep1][merged_gap_a][keep2][merged_gap_b+gap_c]
// Should be 2 free blocks (gap_a and gap_b+gap_c are separate, keep2 sits between)
lib_cbmio_printlf("after defrag:")
print_stats()
lib_cbmio_lf()
// Now free keep1 and keep2 and defrag again to show full coalesce
lib_cbmio_printlf("freeing keep1 and keep2")
lib_mem_free(keep1)
lib_mem_free(keep2)
lib_cbmio_printlf("all freed")
lib_cbmio_lf()
lib_cbmio_print("running defrag...")
lib_mem_defrag()
lib_cbmio_printlf("done")
lib_cbmio_lf()
lib_cbmio_printlf("after full defrag:")
print_stats()
lib_cbmio_lf()
lib_cbmio_printlf("done!")
FEND
LABEL start
main()
SUBEND

View file

@ -0,0 +1 @@
x64 -autostartprgmode 1 memlib_demo2.prg

View file

@ -1,6 +1,6 @@
//-----------------------------------------------------------
//
// Memory library ( Malloc, Free )
// Memory library ( Malloc, Free, Defrag )
//
//
// Author: Mattias Hansson
@ -11,10 +11,13 @@
// Target: generic 6502
//
// Purpose: Dynamic memory allocation using free list
// with heap defragmentation support
//
// Modernized with:
// - Free list algorithm
// - ZP pointer for array-style access
// - Heap defragmentation (coalesces adjacent free blocks)
// - Free list statistics
//-----------------------------------------------------------
//-----------------------------------------------------------
@ -31,6 +34,13 @@
//
// ZP usage (can be overridden):
// - $fa-$fb: current block pointer
//
// Functions:
// lib_mem_init - Initialize heap with one large free block
// lib_mem_malloc - Allocate memory block
// lib_mem_free - Release allocated memory block
// lib_mem_defrag - Coalesce adjacent free blocks (maintenance)
// lib_mem_stats - Return free list statistics
//-----------------------------------------------------------
#IFNDEF __LIB_MEM
@ -166,6 +176,128 @@ FUNC lib_mem_free({WORD m_ptr})
FEND
//-----------------------------------------------------------
// Defrag: Walk heap and merge adjacent free blocks
//-----------------------------------------------------------
FUNC lib_mem_defrag
WORD current @ __LIB_MEM_ZP_CURRENT
WORD scan_addr
WORD next_addr
WORD block_size
WORD next_size
WORD prev_free
WORD free_node
WORD next_next
WORD CONST memstart = __LIB_MEM_START
WORD CONST memend = __LIB_MEM_END
scan_addr = memstart
WHILE scan_addr < memend
current = scan_addr
block_size = PEEKW current[0]
IF block_size = 0
EXIT
ENDIF
current = lib_memlib_free_head
prev_free = NULL
free_node = NULL
WHILE current != NULL
IF current = scan_addr
free_node = current
BREAK
ENDIF
prev_free = current
WORD _tmpx
_tmpx = PEEKW current[2]
current = _tmpx
WEND
IF free_node != NULL
next_addr = scan_addr + block_size
WHILE next_addr < memend
current = next_addr
next_size = PEEKW current[0]
IF next_size = 0
EXIT
ENDIF
current = lib_memlib_free_head
prev_free = NULL
free_node = NULL
WHILE current != NULL
IF current = next_addr
free_node = current
BREAK
ENDIF
prev_free = current
_tmpx = PEEKW current[2]
current = _tmpx
WEND
IF free_node = NULL
BREAK
ENDIF
current = next_addr
next_next = PEEKW current[2]
IF prev_free = NULL
lib_memlib_free_head = next_next
ELSE
current = prev_free
POKEW current[2] , next_next
ENDIF
block_size = block_size + next_size
current = scan_addr
POKEW current[0] , block_size
next_addr = scan_addr + block_size
WEND
ENDIF
current = scan_addr
block_size = PEEKW current[0]
scan_addr = scan_addr + block_size
WEND
FEND
//-----------------------------------------------------------
// Stats: Return free list statistics
// free_blocks - Number of entries in the free list
// total_free - Total free bytes
// largest_free - Size of largest free block
//-----------------------------------------------------------
FUNC lib_mem_stats(out:{WORD free_blocks} out:{WORD total_free} out:{WORD largest_free})
WORD current @ __LIB_MEM_ZP_CURRENT
WORD block_size
free_blocks = 0
total_free = 0
largest_free = 0
current = lib_memlib_free_head
WHILE current != NULL
block_size = PEEKW current[0]
free_blocks = free_blocks + 1
total_free = total_free + block_size
IF block_size > largest_free
largest_free = block_size
ENDIF
WORD _tmpx
_tmpx = PEEKW current[2]
current = _tmpx
WEND
FEND
LABEL lib_memlib_skip
#PRAGMA _P_USE_LONG_JUMP 0