; vim: ts=4:et:sw=4:
; Copyleft (K) by Jose M. Rodriguez de la Rosa
;  (a.k.a. Boriel)
;  http://www.boriel.com
;
; This ASM library is licensed under the MIT license
; you can use it for any purpose (even for commercial
; closed source programs).
;
; Please read the MIT license on the internet

; ----- IMPLEMENTATION NOTES ------
; The heap is implemented as a linked list of free blocks.

; Each free block contains this info:
;
; +----------------+ <-- HEAP START
; | Size (2 bytes) |
; |        0       | <-- Size = 0 => DUMMY HEADER BLOCK
; +----------------+
; | Next (2 bytes) |---+
; +----------------+ <-+
; | Size (2 bytes) |
; +----------------+
; | Next (2 bytes) |---+
; +----------------+   |
; | <free bytes...>|   | <-- If Size > 4, then this contains (size - 4) bytes
; | (0 if Size = 4)|   |
; +----------------+ <-+
; | Size (2 bytes) |
; +----------------+
; | Next (2 bytes) |---+
; +----------------+   |
; | <free bytes...>|   |
; | (0 if Size = 4)|   |
; +----------------+   |
;   <Allocated>        | <-- This zone is in use (Already allocated)
; +----------------+ <-+
; | Size (2 bytes) |
; +----------------+
; | Next (2 bytes) |---+
; +----------------+   |
; | <free bytes...>|   |
; | (0 if Size = 4)|   |
; +----------------+ <-+
; | Next (2 bytes) |--> NULL => END OF LIST
; |    0 = NULL    |
; +----------------+
; | <free bytes...>|
; | (0 if Size = 4)|
; +----------------+


; When a block is FREED, the previous and next pointers are examined to see
; if we can defragment the heap. If the block to be freed is just next to the
; previous, or to the next (or both) they will be converted into a single
; block (so defragmented).


;   MEMORY MANAGER
;
; This library must be initialized calling __MEM_INIT with
; HL = BLOCK Start & DE = Length.

; An init directive is useful for initialization routines.
; They will be added automatically if needed.

#include once <error.asm>
#include once <mem/heapinit.asm>


; ---------------------------------------------------------------------
; MEM_ALLOC
;  Allocates a block of memory in the heap.
;
; Parameters
;  BC = Length of requested memory block
;
; Returns:
;  HL = Pointer to the allocated block in memory. Returns 0 (NULL)
;       if the block could not be allocated (out of memory)
; ---------------------------------------------------------------------

    push namespace core

MEM_ALLOC:
__MEM_ALLOC: ; Returns the 1st free block found of the given length (in BC)
    PROC

    LOCAL __MEM_LOOP
    LOCAL __MEM_DONE
    LOCAL __MEM_SUBTRACT
    LOCAL __MEM_START
    LOCAL TEMP, TEMP0

TEMP EQU TEMP0 + 1

    ld hl, 0
    ld (TEMP), hl

__MEM_START:
    ld hl, ZXBASIC_MEM_HEAP  ; This label point to the heap start
    inc bc
    inc bc  ; BC = BC + 2 ; block size needs 2 extra bytes for hidden pointer

__MEM_LOOP:  ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE
    ld a, h ;  HL = NULL (No memory available?)
    or l
#ifdef __MEMORY_CHECK__
    ld a, ERROR_OutOfMemory
    jp z, __ERROR
#else
    ret z ; NULL
#endif
    ; HL = Pointer to Free block
    ld e, (hl)
    inc hl
    ld d, (hl)
    inc hl          ; DE = Block Length

    push hl         ; HL = *pointer to -> next block
    ex de, hl
    or a            ; CF = 0
    sbc hl, bc      ; FREE >= BC (Length)  (HL = BlockLength - Length)
    jp nc, __MEM_DONE
    pop hl
    ld (TEMP), hl

    ex de, hl
    ld e, (hl)
    inc hl
    ld d, (hl)
    ex de, hl
    jp __MEM_LOOP

__MEM_DONE:  ; A free block has been found.
    ; Check if at least 4 bytes remains free (HL >= 4)
    push hl
    exx  ; exx to preserve bc
    pop hl
    ld bc, 4
    or a
    sbc hl, bc
    exx
    jp nc, __MEM_SUBTRACT
    ; At this point...
    ; less than 4 bytes remains free. So we return this block entirely
    ; We must link the previous block with the next to this one
    ; (DE) => Pointer to next block
    ; (TEMP) => &(previous->next)
    pop hl     ; Discard current block pointer
    push de
    ex de, hl  ; DE = Previous block pointer; (HL) = Next block pointer
    ld a, (hl)
    inc hl
    ld h, (hl)
    ld l, a    ; HL = (HL)
    ex de, hl  ; HL = Previous block pointer; DE = Next block pointer
TEMP0:
    ld hl, 0   ; Pre-previous block pointer

    ld (hl), e
    inc hl
    ld (hl), d ; LINKED
    pop hl ; Returning block.

    ret

__MEM_SUBTRACT:
    ; At this point we have to store HL value (Length - BC) into (DE - 2)
    ex de, hl
    dec hl
    ld (hl), d
    dec hl
    ld (hl), e ; Store new block length

    add hl, de ; New length + DE => free-block start
    pop de     ; Remove previous HL off the stack

    ld (hl), c ; Store length on its 1st word
    inc hl
    ld (hl), b
    inc hl     ; Return hl
    ret

    ENDP

    pop namespace

