diff --git a/AGENTS.md b/AGENTS.md index a1c7fbb..0586ce8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/examples/memlib_demo2/cm.sh b/examples/memlib_demo2/cm.sh new file mode 100644 index 0000000..6335c8d --- /dev/null +++ b/examples/memlib_demo2/cm.sh @@ -0,0 +1,3 @@ +#!/bin/sh +PROGNAME="memlib_demo2" +c65gm ${PROGNAME}.c65 diff --git a/examples/memlib_demo2/memlib_demo2.c65 b/examples/memlib_demo2/memlib_demo2.c65 new file mode 100644 index 0000000..f53f332 --- /dev/null +++ b/examples/memlib_demo2/memlib_demo2.c65 @@ -0,0 +1,197 @@ +//----------------------------------------------------------- +// Memory Library Demo 2: Defrag & Stats +// Demonstrates heap defragmentation and statistics +//----------------------------------------------------------- + +#PRAGMA _P_REMOVE_UNUSED 1 + +#INCLUDE +#INCLUDE +#INCLUDE +#INCLUDE + +#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 diff --git a/examples/memlib_demo2/start_in_vice.sh b/examples/memlib_demo2/start_in_vice.sh new file mode 100644 index 0000000..24e2718 --- /dev/null +++ b/examples/memlib_demo2/start_in_vice.sh @@ -0,0 +1 @@ +x64 -autostartprgmode 1 memlib_demo2.prg diff --git a/lib/memlib.c65 b/lib/memlib.c65 index e608dad..a12a8fa 100644 --- a/lib/memlib.c65 +++ b/lib/memlib.c65 @@ -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