
options.DebugHeap = 0


module "Heap manager"


include "%TargetOS%/heapmgr.asm"


ERR_FASMLIB = 0                 ;Internal FASMLIB assertion failed. This is bug in FASMLIB. Please report
ERR_UNKNOWN = 1                 ;Unknown error
ERR_MODULE_NOT_INITIALIZED = 3  ;(internal) Module not initialized
ERR_OUT_OF_MEM = 4              ;Out of memory
ERR_HEAP_CORRUPT = 5            ;(internal) Heap corrupt
ERR_MEMORY_LEAK = 6             ;(internal) Memory leak
ERR_INVALID_POINTER = 7         ;(internal) Invalid pointer
ERR_OUT_OF_RANGE = 12           ;Out of range
ERR_ZERO_SIZE = 14              ;(internal) Zero size


;interface of HEAPBLOCK and heap manager constants
struct HEAPBLOCK
  .sign  dw 'HB'         ;signature of block. must be 'HB' (heap block)
  .used  db ?            ;0 = block is unused, 1 = block is used
  .last  db ?            ;0 = block isn't last, 1 = block is last
                         ;only valid for used blocks
  .size  dd ?            ;real size of this block (not including header)
  .prev  dd ?            ;pointer to previous block. 0=this is first block
  .asize dd ?            ;allocated size of this block (can be smaller than real size)
ends


;heap size alignment (default = 4KB)
;it's value must be power of two!!!
match =HEAP_SIZE_ALIGN, HEAP_SIZE_ALIGN {
  HEAP_SIZE_ALIGN equ 4096
}

;intial heap size (default = 16KB)
;must be multiple of HEAP_SIZE_ALIGN
match =HEAP_INIT_SIZE, HEAP_INIT_SIZE {
  HEAP_INIT_SIZE equ 16384
}

;sizes of block will be multiples of this value (default = 8 bytes)
;any smaller free block will be left out as padding
;it's value must be power of two!!!
match =HEAP_BLOCK_ALIGN, HEAP_BLOCK_ALIGN {
  HEAP_BLOCK_ALIGN equ 16
}

;minimal size of block, when it is split from block whose size shrinked
;must be multiple of HEAP_BLOCK_ALIGN
match =HEAP_MIN_SPLIT_SIZE, HEAP_MIN_SPLIT_SIZE {
  HEAP_MIN_SPLIT_SIZE equ HEAP_BLOCK_ALIGN
}



iglobal
  var mem.initialized = 0    ;0 = module not initialized, 1=initialized
endg


uglobal
  var mem.heap.start = ?      ;pointer to beginning of heap
  var mem.heap.size = ?       ;size of heap
  var mem.alloc_count  = ?    ;number of allocations (to discover memory leak)
  var mem.heap_corrupt = ?    ;0 = okay, 1 = heap corrupt

  if used mem.access_mutex
    align 4
    mem.access_mutex TMutex
  end if
endg




if used GetMem
;============================================================================
; mem.init
; desc: initalizes mem module (and it's heap manager)
; args: none
; ret: CF set on error
; note: initialization is required for allocation
; error: ERR_UNKNOWN - platform-specific error
;        +mem.alloc_heap (in platform-dependant part of mem module)
;============================================================================
initialize mem.init
begin
        ;if module is already initialized, just return
        cmp     [mem.initialized], 0
        jne     .rnc

        ; call platform-dependant heap allocation
        stdcall mem.alloc_heap, HEAP_INIT_SIZE
        jc      .rc

        ; save heap info
        mov     [mem.heap.start], eax
        mov     [mem.heap.size], HEAP_INIT_SIZE

        ; create first heap block
        mov     [eax + HEAPBLOCK.sign], 'HB'
        mov     [eax + HEAPBLOCK.size], HEAP_INIT_SIZE - sizeof.HEAPBLOCK
        mov     [eax + HEAPBLOCK.prev], 0
        mov     [eax + HEAPBLOCK.last], 1
        mov     [eax + HEAPBLOCK.used], 0

        ; initialized
        mov     [mem.alloc_count], 0
        mov     [mem.heap_corrupt], 0
        mov     [mem.initialized], 1

        if options.Threads
          stdcall MutexCreate, 0, mem.access_mutex
          stdcall MutexRelease, mem.access_mutex
        end if


.rnc:   clc
.r:     return

.rc:    stc
        return
endp




;============================================================================
; mem.uninit
; desc: uninitalizes mem module (and it's heap manager)
; args: none
; ret: CF set on error
; error: ERR_UNKNOWN     - platform-specific error
;        ERR_MEMORY_LEAK - you didn't free some allocated block
;        +mem.free_heap (in platform-dependant part of mem module)
;============================================================================
finalize mem.uninit
begin
  if options.Threads
        stdcall WaitForMutex, mem.access_mutex, -1
        stdcall MutexDestroy, mem.access_mutex
  end if

        ;is module initialized?
        cmp     [mem.initialized], 0
        jz      .error_not_initialized

        cmp     [mem.heap_corrupt],0
        jne     .dont_test_heap

        ; check heap
        stdcall __mem.test_heap
        jc      .rc

.dont_test_heap:

        ; call platform-dependant free heap
        stdcall mem.free_heap, [mem.heap.start], [mem.heap.size]
        jc      .rc

        ; uninitialized
        mov     [mem.initialized], 0

        ; return error if there is some memory leak
        cmp     [mem.alloc_count], 0
        ja      .error_memory_leak

        clc
        return

.rc:    mov     [mem.initialized], 0
        stc
        return

.error_memory_leak:
        mov     eax, ERR_MEMORY_LEAK
        jmp     .rc

.error_not_initialized:
        mov     eax, ERR_MODULE_NOT_INITIALIZED
        jmp     .rc
endp


end if


uglobal
  if used LogMemory
    align 4
    __LogMemoryMutex TMutex
  end if
endg


if used LogMemory

initialize InitLogMemory
begin
        stdcall MutexCreate, 0, __LogMemoryMutex
        stdcall MutexRelease, __LogMemoryMutex
        return
endp

finalize FreeLogMemory
begin
        stdcall MutexDestroy, __LogMemoryMutex
        return
endp

end if


proc LogMemory, .pmsg, .address, .argcount
begin
        pushad

        mov     esi, [ebp]      ; caller stack frame.
        mov     edi, [esi]      ; one level back.

        cmp     edi, ebp
        ja      .ediok

        mov     edi, esi
.search:
        add     edi, 4
        cmp     [edi], esp
        jb      .search

.ediok:
        stdcall WaitForMutex, __LogMemoryMutex, -1

        stdcall Output, [.pmsg]
        stdcall OutputNumber, [.address], 16, 8
        stdcall Output, '; Call from: $'
        stdcall OutputNumber, [esi+4], 16, 8

        stdcall Output, <',',' $'>
        stdcall OutputNumber, [edi+4], 16, 8

        xor     ecx, ecx
        inc     ecx
.argloop:
        stdcall Output, ';  Arg('
        stdcall OutputNumber, ecx, 10, 2
        stdcall Output, ') = $'
        stdcall OutputNumber, [esi+4*ecx+4], 16, 8
        inc     ecx
        cmp     ecx, [.argcount]
        jbe     .argloop

        stdcall Output, .cCRLF

        stdcall MutexRelease, __LogMemoryMutex
        popad
        return

.cCRLF db 13, 10, 0
endp




body GetMem
begin
  if options.Threads
        stdcall WaitForMutex, mem.access_mutex, -1
        jc      .errmutex
  end if

        stdcall mem.alloc0, [.size]

  if options.Threads
        stdcall MutexRelease, mem.access_mutex
  end if
        jc      .error

if options.DebugHeap
        stdcall LogMemory, 'Allocate:', eax, 1
        clc
end if
        return

.error:
if options.DebugHeap
        stdcall LogMemory, 'Allocate error:', eax, 1
        stc
end if
        xor     eax, eax
        return

.errmutex:
if options.DebugHeap
        stdcall LogMemory, 'Mutex timeout:', eax, 1
        stc
end if
        jmp     .error

endp


body FreeMem
begin
        push    eax

  if options.Threads
        stdcall WaitForMutex, mem.access_mutex, -1
        jc      .finish
  end if

        stdcall mem.free, [.ptr]

  if options.Threads
        stdcall MutexRelease, mem.access_mutex
  end if

if options.DebugHeap
        jnc     .ok
        stdcall LogMemory, 'Free error:', [.ptr], 1
        stc
        jmp     .finish

.ok:
        stdcall LogMemory, 'Free:', [.ptr], 1
        clc
end if

.finish:
        pop     eax
        return
endp




body ResizeMem
begin
if options.DebugHeap
        stdcall LogMemory, 'Reallocate attempt:', [.ptr], 2
end if

  if options.Threads
        stdcall WaitForMutex, mem.access_mutex, -1
        jc      .error
  end if

        stdcall mem.realloc0, [.ptr], [.newsize]

  if options.Threads
        stdcall MutexRelease, mem.access_mutex
  end if
        jnc     .finish

.error:
        mov     eax, [.ptr]

if options.DebugHeap
        jnc     .ok
        stdcall LogMemory, 'Reallocate failed:', eax, 2
        stc
        jmp     .finish

.ok:
        stdcall LogMemory, 'Reallocate success:', eax, 2
        clc
end if

.finish:
        return
endp




;; @name mem.alloc0
;; @desc
;;    Allocates block of memory, and fills it with zeroes.
;; @arg size
;;    Size of memory to allocate
;; @ret
;;    CF set on error, otherwise
;;    EAX = pointer to beginning of allocated memory block
;; @err ERR_OUT_OF_MEM
;;    Not enough memory
;; @err ERR_ZERO_SIZE
;;    size = 0
;; @err ERR_OUT_OF_RANGE
;;     size is negative (or larger than 7FFFFFFFh if viewed as unsigned)
;; @err ERR_HEAP_CORRUPT
;;    {heap:Heap is corrupted}. You was writing to memory outside of allocated block.
;; @err ERR_MODULE_NOT_INITIALIZED
;;    Module is not initialized. Initialize module with {mem.init} to use this procedure.
;; @err ERR_UNKNOWN
;;    System returned error that wasn't translated to FASMLIB error.
;;    You can {contact} author about this, and translation can be added.
;; @warn Don't forget to release allocated block with {mem.free}!
;; @note returned pointer must be always >= 10000h
;; @note Returned pointer is always >= 10000h to make it easily distinguishable from handles.
;; @note Maximal allowed size is 7FFFFFFFh, to catch errors with negative values.
;; @note Altough returned block may have bigger size than requested on some implementations,
;;       don't use this additional memory.
;;       It is not zeroes, an {mem.realloc} doesn't preserve this memory.
;; @note Memory management must be {mem.init:initialized} for this procedure to work.
;; @note This is platform-specific procedure
proc mem.alloc0, .size
begin
        push    ebx

        ;error if size=0
        cmp     [.size], 0
        je      .error_size_zero

        ;allocate
        stdcall mem.alloc, [.size]
        jc      .rc
        mov     ebx, eax

        ;zero memory
        stdcall mem.fill, ebx, [.size], 0
        jc      .rc

        mov     eax, ebx

.rnc:   clc
.r:     pop     ebx
        return

.rc:    stc
        int3
        jmp     .r

.error_size_zero:
        mov     eax, ERR_ZERO_SIZE
        jmp     .rc
endp




;============================================================================
;; @name mem.realloc0
;; @desc
;;    Rellocates (resizes) block of memory.
;;    Block can be moved during this process, so you need to update pointer(s) to it.
;;    Contents of block are preserved.
;;    If block is enlarging, contents of added space are filled with zeroes.
;; @arg blockptr
;;    Pointer to block to relocate.
;;    This must be same pointer as returned from allocation function, pointer into block is not enough.
;; @arg newsize
;;    New size of block, in bytes
;; @ret
;;    CF set on error, orherwise
;;    EAX = pointer to reallocated memory block
;; @err ERR_INVALID_POINTER
;;    Blockptr is not a valid allocated block pointer.
;;    This could also mean {heap:heap corruption}, these two cases cannot be properly distinguished.
;; @err ERR_OUT_OF_MEM
;;     Not enough memory
;; @err ERR_ZERO_SIZE
;;     size = 0
;; @err ERR_OUT_OF_RANGE
;;     size is negative (or larger than 7FFFFFFFh if viewed as unsigned)
;; @err ERR_HEAP_CORRUPT
;;     {heap:Heap is corrupted}. You was writing to memory outside of allocated block.
;; @err ERR_MODULE_NOT_INITIALIZED
;;      Module is not initialized. Initialize module with {mem.init} to use this procedure.
;; @err ERR_UNKNOWN
;;     System returned error that wasn't translated to FASMLIB error.
;;     You can {contact} author about this, and translation can be added.
;; @dep mem.realloc_heap
;;     Only on platforms where FASMLIB heap manager is used.
;; @warn
;;     Block can be moved during realocation! If you had any pointers to block,
;;     they become invalid (so rather prefer using indexes to allocated blocks).
;;     If you saved block pointer in variable, you need to rewrite it with
;;     returned value.
;; @note Returned pointer is always >= 10000h to make it easily distinguishable from handles.
;; @note Maximal allowed newsize is 7FFFFFFFh, to catch errors with negative values.
;; @note Altough returned block may have bigger size than requested on some implementations,
;;       don't use this additional memory. {mem.realloc} doesn't preserve this memory.
;; @note Memory management must be {mem.init:initialized} for this procedure to work.
;; @note This is platform-specific procedure
proc mem.realloc0, .blockptr, .newsize
begin
        push    ebx esi

        ;error if newsize=0
        cmp     [.newsize], 0
        je      .error_newsize_zero

        ;ESI = save old block size
        stdcall GetMemSize, [.blockptr]
        mov     esi, eax

        ;reallocate
        stdcall mem.realloc, [.blockptr], [.newsize]
        jc      .r
        mov     ebx, eax

        ;number of bytes to zero = newsize-oldsize
        mov     eax, [.newsize]
        sub     eax, esi

        ;zero new memory
        pushd   0
        pushd   eax
        lea     eax, [ebx+esi]
        pushd   eax
        call    mem.fill
        jc      .r

        mov     eax, ebx

.rnc:   clc
.r:     pop     esi ebx
        return

.rc:    stc
        int3
        jmp     .r

.error_newsize_zero:
        mov     eax, ERR_ZERO_SIZE
        jmp     .rc

endp




;============================================================================
; mem.alloc
; desc: allocates <size> bytes of memory
; args: size - size of memory to allocate
; ret: CF set on error, otherwise
;      eax = pointer to beginning of allocated memory block
; note: - returned pointer must be always >= 10000h
;       - contents of allocated memory are unknown
;         (use alloc0 to have block zeroed)
;       - don't forget to mem.free allocated memory!
;       - maximal allowed size if 7FFFFFFFh, to catch errors with negative value
; error: ERR_OUT_OF_MEM   - out of memory
;        ERR_ZERO_SIZE    - size=0
;        ERR_OUT_OF_RANGE - size >= 80000000h
;        ERR_HEAP_CORRUPT - you was overwriting memory outside allocated block
;        ERR_UNKNOWN
;        ERR_MODULE_NOT_INITIALIZED
;        mem.heap_alloc
;============================================================================
proc mem.alloc, .size
begin
        push    ebx ecx edx esi edi

        ;is module initialized?
        cmp     [mem.initialized], 0
        je      .error_not_initialized

        ;was heap corruption detected?
        cmp     [mem.heap_corrupt], 0
        jne     .error_heap_corrupt

        ;error if size=0
        cmp     [.size], 0
        je      .error_size_zero
        jl      .error_size_too_big

        ;ECX = size to allocate, aligned to HEAP_BLOCK_ALIGN
        mov     ecx, [.size]
        add     ecx, HEAP_BLOCK_ALIGN-1
        and     ecx, not (HEAP_BLOCK_ALIGN-1)

        ;EBX = pointer to first HEAP block
        mov     ebx, [mem.heap.start]

        ;find unused block with enough space
.find_block:

        ;check if block is okay
        cmp     [ebx + HEAPBLOCK.sign], 'HB'
        jne     .error_heap_corrupt

        ;is the block unused?
        cmp     [ebx + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        je      .try_next_block

        ;is the block large enough?
        cmp     [ebx + HEAPBLOCK.size], ecx
        jae     .block_found

.try_next_block:

        ;is there some next block?
        cmp     [ebx + HEAPBLOCK.last], 1
        ja      .error_heap_corrupt
        je      .no_block_found

        ;go to next block
        add     ebx, [ebx + HEAPBLOCK.size]
        add     ebx, sizeof.HEAPBLOCK
        jmp     .find_block

        ;no suitable block found. we need to enlarge heap
        ;EBX = pointer to last block
.no_block_found:
.resize_heap:

        ; increasize heap size by needed size + header size aligned to HEAP_SIZE_ALIGN
        mov     edx, ecx
        add     edx, sizeof.HEAPBLOCK
        add     edx, HEAP_SIZE_ALIGN-1
        and     edx, not (HEAP_SIZE_ALIGN-1)
        add     edx, [mem.heap.size]
        stdcall mem.realloc_heap, [mem.heap.start], edx, [mem.heap.size]
        mov     [mem.heap_corrupt], 1  ;if this call failed, we are screwed
        jc      .rc
        mov     [mem.heap_corrupt], 0
        xchg    [mem.heap.size], edx

        ;edx = size of added block
        mov     eax, [mem.heap.size]
        sub     eax, edx
        mov     edx, eax

        ;if last block was free, we append new space to it
        cmp     [ebx + HEAPBLOCK.used], 1
        jne     .add_new_space_to_last_block

        ;last block is used, but first we need to check if we can take enough unused space from last block
        ;if there is at least sizeof.HEAPBLOCK unused bytes after alignation, we take them
        mov     esi, [ebx + HEAPBLOCK.asize]
        add     esi, HEAP_BLOCK_ALIGN-1
        and     esi, not (HEAP_BLOCK_ALIGN-1)
        mov     eax, [ebx + HEAPBLOCK.size]
        sub     eax, esi
        jc      .create_block_in_added_space  ;if size is not even aligned. shouldn't happen but anyway...

        cmp     eax, sizeof.HEAPBLOCK
        jb      .create_block_in_added_space

        ;EBX = last block, ESI = new size of EBX block
.take_space_from_last_block:

        ;set up EBX
        mov     [ebx + HEAPBLOCK.last], 0
        mov     [ebx + HEAPBLOCK.size], esi

        ;set up ESI
        lea     esi, [ebx + esi + sizeof.HEAPBLOCK]
        mov     [esi + HEAPBLOCK.sign], 'HB'
        mov     [esi + HEAPBLOCK.last], 1
        mov     [esi + HEAPBLOCK.prev], ebx
        mov     eax, [mem.heap.start]
        add     eax, [mem.heap.size]
        sub     eax, esi
        sub     eax, sizeof.HEAPBLOCK
        mov     [esi + HEAPBLOCK.size], eax  ;size = up to end of block
        mov     [esi + HEAPBLOCK.used], 0

        mov     ebx, esi
        jmp     .block_found

.create_block_in_added_space:

        ;esi = new block
        lea     esi, [ebx + sizeof.HEAPBLOCK]
        add     esi, [ebx + HEAPBLOCK.size]

        ;set up the block
        mov     [ebx + HEAPBLOCK.last], 0
        mov     [esi + HEAPBLOCK.sign], 'HB'
        mov     [esi + HEAPBLOCK.last], 1
        mov     [esi + HEAPBLOCK.prev], ebx
        sub     edx, sizeof.HEAPBLOCK
        mov     [esi + HEAPBLOCK.size], edx
        mov     [esi + HEAPBLOCK.used], 0

        mov     ebx, esi
        jmp     .block_found


.add_new_space_to_last_block:
        add     [ebx + HEAPBLOCK.size], edx

        ;suitable block found, we will use it for allocation
        ;EBX = pointer to block, ecx=size to allocate
.block_found:

        ;EDI = beginning of new block aligned
        lea     edi, [ebx + sizeof.HEAPBLOCK + ecx]

        ;EDX = space left in block
        mov     edx, [ebx + HEAPBLOCK.size]
        sub     edx, ecx
        jz      .set_block ;if zero, we can skip it

        ;ESI = next block
        cmp     [ebx + HEAPBLOCK.last], 1
        ja      .error_heap_corrupt
        je      .is_last_block
        lea     esi, [ebx + sizeof.HEAPBLOCK]
        add     esi, [ebx + HEAPBLOCK.size]

        jmp     .not_last_block


.is_last_block:
        sub     edx, sizeof.HEAPBLOCK

        ;if following space is not big enough, leave it as padding
        cmp     edx, HEAP_BLOCK_ALIGN
        jb      .set_block

        ;there is enough space, create new block (EDI)
        mov     [ebx + HEAPBLOCK.last], 0
        mov     [ebx + HEAPBLOCK.size], ecx
        mov     [edi + HEAPBLOCK.sign], 'HB'
        mov     [edi + HEAPBLOCK.used], 0
        mov     [edi + HEAPBLOCK.last], 1
        mov     [edi + HEAPBLOCK.size], edx
        mov     [edi + HEAPBLOCK.prev], ebx

        jmp     .set_block

.not_last_block:

        ;if next block is unused, move it to rest of buffer append space
        cmp     [esi + HEAPBLOCK.used], 0
        jne     .next_block_used

.next_block_unused:

        ;no moving if gained space is <= sizeof.HEAPBLOCK (could case problems)
        cmp     edx, sizeof.HEAPBLOCK
        jbe     .set_block

        ;next block is unused, move it to EDI
        mov     [ebx + HEAPBLOCK.size], ecx
        mov     [esi + HEAPBLOCK.sign], 0 ;clean sign of the next block, to prevent identifying it as valid block in future
        mov     [edi + HEAPBLOCK.sign], 'HB'
        mov     [edi + HEAPBLOCK.used], 0     ;newblock.used = 0
        mov     al, [esi + HEAPBLOCK.last]
        mov     [edi + HEAPBLOCK.last], al
        mov     eax, [esi + HEAPBLOCK.size]
        add     eax, [edx - sizeof.HEAPBLOCK]
        add     [edi + HEAPBLOCK.size], eax   ;newblock.size = newblock.size + nextblock.size - nextblock_header_size
        mov     [edi + HEAPBLOCK.prev], ebx   ;newblock.prev = previous block (EBX)
        mov     [eax + HEAPBLOCK.prev], edi   ;nextblock.next.prev = newblock

        jmp     .set_block

        ;following block (ESI) is used
.next_block_used:
        sub     edx, sizeof.HEAPBLOCK

        ;if remaining space is not big enough to create new block, leave it as padding
        cmp     edx, HEAP_BLOCK_ALIGN
        jb      .set_block

        ;it's big enough, so create new block
        mov     [ebx + HEAPBLOCK.size], ecx
        mov     [edi + HEAPBLOCK.sign], 'HB'
        mov     [edi + HEAPBLOCK.size], edx
        mov     [edi + HEAPBLOCK.prev], ebx
        mov     [edi + HEAPBLOCK.used], 0
        mov     [edi + HEAPBLOCK.last], 0
        lea     eax, [edi + edx + sizeof.HEAPBLOCK]
        mov     [eax + HEAPBLOCK.prev], edi             ;ebx->next->prev = ebx->next

        ;set current block we are going to allocate
.set_block:
        mov     eax, [.size]
        mov     [ebx + HEAPBLOCK.asize], eax
        mov     [ebx + HEAPBLOCK.used], 1

        ;another block successfully allocated
        inc     [mem.alloc_count]

        ;return pointer to block
        lea     eax, [ebx + sizeof.HEAPBLOCK]

.rnc:   clc
.r:     pop     edi esi edx ecx ebx
        return

.rc:
        DebugMsg "Error mem.alloc"
        stc
        jmp     .r

.error_not_initialized:
        mov     eax, ERR_MODULE_NOT_INITIALIZED
        jmp     .rc

.error_heap_corrupt:
        int3
        mov     [mem.heap_corrupt], 1
        mov     eax, ERR_HEAP_CORRUPT
        jmp     .rc

.error_size_zero:
        mov     eax, ERR_ZERO_SIZE
        jmp     .rc

.error_size_too_big:
        mov     eax, ERR_OUT_OF_RANGE
        jmp     .rc

endp






;=============================================================================
; mem.free
; desc: deallocates memory block
; args: blockptr - pointer to memory block returned by mem.alloc
; ret: CF set on error
; note: after error, consider block as freed, don't use it anymore
; error: ERR_INVALID_POINTER - if blockptr isn't valid block pointer
;        ERR_HEAP_CORRUPT - you was overwriting memory outside allocated block
;        ERR_UNKNOWN
;        ERR_MODULE_NOT_INITIALIZED
;        ERR_FASMLIB
;=============================================================================
proc mem.free, .blockptr
begin
        push    eax ebx edi

        ;is module initialized?
        cmp     [mem.initialized], 0
        je      .error_not_initialized

        ;was heap corruption detected?
        cmp     [mem.heap_corrupt], 0
        jne     .error_heap_corrupt

        ;EBX = pointer to heap block
        mov     ebx, [.blockptr]
        sub     ebx, sizeof.HEAPBLOCK

        ;check if pointer lies inside heap
        mov     eax, ebx
        sub     eax, [mem.heap.start]
        jb      .error_bad_pointer
        sub     eax, [mem.heap.size]
        jae     .error_bad_pointer

        ;check block
        cmp     [ebx + HEAPBLOCK.sign], 'HB'
        jne     .error_bad_pointer
        cmp     [ebx + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        jb      .error_bad_pointer

        ;free block
        mov     [ebx + HEAPBLOCK.used], 0

.check_following_block:

        ;EDI = following block (if any)
        cmp     [ebx + HEAPBLOCK.last], 1
        ja      .error_heap_corrupt
        je      .check_previous_block
        lea     edi, [ebx + sizeof.HEAPBLOCK]
        add     edi, [ebx + HEAPBLOCK.size]

        ;check following block
        cmp     [edi + HEAPBLOCK.sign], 'HB'
        jne     .error_heap_corrupt
        cmp     [edi + HEAPBLOCK.prev], ebx
        jne     .error_heap_corrupt

        ;if following block is unused, append it
        cmp     [edi + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        je      .check_previous_block

        ;append block
        mov     [edi + HEAPBLOCK.sign], 0
        mov     eax, [edi + HEAPBLOCK.size]
        add     eax, sizeof.HEAPBLOCK
        add     [ebx + HEAPBLOCK.size], eax
        mov     al, [edi + HEAPBLOCK.last]
        mov     [ebx + HEAPBLOCK.last], al

.check_previous_block:

        ;EDI = previous block (if any)
        mov     edi,[ebx + HEAPBLOCK.prev]
        test    edi, edi
        jz      .done

        ;check EDI block
        cmp     [edi + HEAPBLOCK.sign], 'HB'
        jne     .error_heap_corrupt
        cmp     [edi + HEAPBLOCK.last], 0
        jne     .error_heap_corrupt
        lea     eax, [edi + sizeof.HEAPBLOCK]
        add     eax, [edi + HEAPBLOCK.size]
        cmp     eax, ebx
        jne     .error_heap_corrupt

        ;if EDI block is unused, append EBX block to to it
        cmp     [edi + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        je      .done

        ;append block
        mov     [ebx + HEAPBLOCK.sign], 0
        mov     eax, [ebx + HEAPBLOCK.size]
        add     eax, sizeof.HEAPBLOCK
        add     [edi + HEAPBLOCK.size], eax
        mov     al, [ebx + HEAPBLOCK.last]
        mov     [edi + HEAPBLOCK.last], al

        mov     ebx, edi

.done:

        ;if this isn't last block, set prev of next block
        cmp     [ebx + HEAPBLOCK.last], 1
        je      @f
        lea     eax, [ebx + sizeof.HEAPBLOCK]
        add     eax, [ebx + HEAPBLOCK.size]
        mov     [eax + HEAPBLOCK.prev], ebx
        @@:

        ;another block successfully released
        dec     [mem.alloc_count]
        jc      .error_alloc_count_overflow

        ;TODO - reduce heap size if ebx is last?

.rnc:   clc
.r:     pop     edi ebx eax
        return

.rc:    stc
        jmp     .r

.error_not_initialized:
        mov     eax, ERR_MODULE_NOT_INITIALIZED
        jmp     .rc

.error_heap_corrupt:
        int3
        mov     [mem.heap_corrupt], 1
        mov     eax, ERR_HEAP_CORRUPT
        jmp     .rc

.error_bad_pointer:
        mov     eax, ERR_INVALID_POINTER
        jmp     .rc

.error_alloc_count_overflow:
        mov     eax, ERR_FASMLIB
        jmp     .rc
endp





;=============================================================================
; mem.realloc
; desc: reallocates memory block
; args: blockptr - pointer to memory block returned by mem.(re)alloc
;       newsize  - new size of memory block
; ret: CF set on error, orherwise
;      eax = pointer to reallocated memory block
; note: returned pointer must be always >= 10000h
;       - data in block are is modified (only data at the end of block is
;         lost if newsize < previous size)
;       - if block is enlarging, then contents of added memory are unknown
;         (use realloc0 to have it zeroed)
;       - block can move to other place in memory, so previous pointers into
;         block become invalid (so rather use indexes in block than pointers)
;       - maximal allowed size if 7FFFFFFFh, to catch errors with negative value
;       - if you had pointer stored in variable, don't forget to restore it:
;           libcall mem.realloc, [blockptr], [newsize]
;           jc error
;           mov [blockptr], eax     ;without this, it's hard-to-find bug
; error: ERR_INVALID_POINTER - blockptr isn't a valid block pointer
;        ERR_HEAP_CORRUPT    - you was overwriting memory outside allocated block
;        ERR_ZERO_SIZE       - newsize = 0
;        ERR_OUT_OF_RANGE    - newsize >= 80000000h
;        ERR_MODULE_NOT_INITIALIZED
;        +mem.alloc
;        +mem.free
;=============================================================================
proc mem.realloc, .blockptr, .newsize
begin
        push    ebx ecx edx esi edi

        ;is module initialized?
        cmp     [mem.initialized], 0
        je      .error_not_initialized

        ;was heap corruption detected?
        cmp     [mem.heap_corrupt], 0
        jne     .error_heap_corrupt

        ;error if newsize=0
        cmp     [.newsize], 0
        je      .error_newsize_zero
        jl      .error_newsize_too_big

        ;EBX = pointer to heap block
        mov     ebx, [.blockptr]
        sub     ebx, sizeof.HEAPBLOCK

        ;check if pointer lies inside heap
        mov     eax, ebx
        sub     eax, [mem.heap.start]
        jb      .error_bad_pointer
        sub     eax, [mem.heap.size]
        jae     .error_bad_pointer

        ;check block
        cmp     [ebx + HEAPBLOCK.sign], 'HB'
        jne     .error_bad_pointer
        cmp     [ebx + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        jb      .error_bad_pointer

        ;if newsize fits into real size of block, just reset size
        mov     ecx, [.newsize]
        cmp     ecx, [ebx + HEAPBLOCK.size]
        ja      .current_block_too_small
        mov     [ebx + HEAPBLOCK.asize], ecx
        jmp     .done

.current_block_too_small:

        ;ECX = requested size of block aligned
        add     ecx, HEAP_BLOCK_ALIGN-1
        and     ecx, not (HEAP_BLOCK_ALIGN-1)

        ;EDX = required additional size
        mov     edx, ecx
        sub     edx, [ebx + HEAPBLOCK.size]

        ;if there is no following block, we must allocate
        cmp     [ebx + HEAPBLOCK.last], 1
        je      .allocate_new_block
        ja      .error_heap_corrupt

        ;ESI = following block
        lea     esi, [ebx + sizeof.HEAPBLOCK]
        add     esi, [ebx + HEAPBLOCK.size]

        ;check following block
        cmp     [esi + HEAPBLOCK.sign], 'HB'
        jne     .error_heap_corrupt
        cmp     [esi + HEAPBLOCK.prev], ebx
        jne     .error_heap_corrupt

        ;if following block is used, we must allocate
        cmp     [esi + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        je      .allocate_new_block

        ;if following block is not big enough, we must allocate
        mov     eax, [esi + HEAPBLOCK.size]
        add     eax, sizeof.HEAPBLOCK
        cmp     edx, eax
        ja      .allocate_new_block

.append_following_block:

        ;EBX = our block, ESI=following block, EAX=size of new block (header included)
        add     [ebx + HEAPBLOCK.size], eax
        mov     eax, [.newsize]
        mov     [ebx + HEAPBLOCK.asize], eax
        mov     [esi + HEAPBLOCK.sign], 0 ;delete mark

        ;if next block was last, set this one as last
        mov     al, [esi + HEAPBLOCK.last]
        mov     [ebx + HEAPBLOCK.last], al
        cmp     al, 1
        ja      .error_heap_corrupt
        je      .block_created

        ;otherwise point nextblock.prev to ours block
        add     esi, [esi + HEAPBLOCK.size] ;move to next block
        add     esi, sizeof.HEAPBLOCK
        mov     [esi + HEAPBLOCK.prev], ebx

        jmp     .block_created

.allocate_new_block:

        ;allocate new block
        mov     ecx, [.newsize]
        stdcall mem.alloc, ecx
        jc      .rc

        ;copy contents of old block into new block
        mov     edi, eax
        lea     esi, [ebx+sizeof.HEAPBLOCK]
        stdcall mem.copy, edi, esi, [ebx+HEAPBLOCK.asize]
        jc      .rc

        ;free old block
        stdcall mem.free, esi
        jc      .rc

        ;return new block
        mov     eax, edi
        jmp     .rnc

.block_created:

        ;now check whether to split unused part from created block

        ;ECX = aligned real size of block
        mov     ecx, [ebx + HEAPBLOCK.asize]
        add     ecx, HEAP_BLOCK_ALIGN-1
        and     ecx, not (HEAP_BLOCK_ALIGN-1)

        ;EDX = gained space
        mov     edx, [ebx + HEAPBLOCK.size]
        sub     edx, ecx

        ;if it's at least sizeof.HEAPBLOCK + HEAP_BLOCK_ALIGN, create new block
        sub     edx, sizeof.HEAPBLOCK
        jb      .done
        cmp     edx, HEAP_BLOCK_ALIGN
        jb      .done

        ;ESI = new block
        mov     [ebx + HEAPBLOCK.size], ecx
        lea     esi, [ebx + ecx + sizeof.HEAPBLOCK]
        mov     [esi + HEAPBLOCK.sign], 'HB'
        mov     [esi + HEAPBLOCK.used], 0
        mov     [esi + HEAPBLOCK.size], edx
        mov     [esi + HEAPBLOCK.prev], ebx
        mov     al, [ebx + HEAPBLOCK.last]
        mov     [ebx + HEAPBLOCK.last], 0
        mov     [esi + HEAPBLOCK.last], al

        ;if esi isn't last, set esi->next->prev = esi
        cmp     al, 1
        ja      .error_heap_corrupt
        je      @f
        lea     eax, [esi + edx + sizeof.HEAPBLOCK]
        mov     [eax + HEAPBLOCK.prev], esi
        @@:

.done:
        ;return pointer
        lea     eax, [ebx + sizeof.HEAPBLOCK]

.rnc:   clc
.r:     pop     edi esi edx ecx ebx
        return

.rc:    stc
        jmp     .r

.error_not_initialized:
        mov     eax, ERR_MODULE_NOT_INITIALIZED
        jmp     .rc

.error_heap_corrupt:
        int3
        mov     [mem.heap_corrupt], 1
        mov     eax, ERR_HEAP_CORRUPT
        jmp     .rc

.error_newsize_zero:
        mov     eax, ERR_ZERO_SIZE
        jmp     .rc

.error_newsize_too_big:
        mov     eax, ERR_OUT_OF_RANGE
        jmp     .rc

.error_bad_pointer:
        mov     eax, ERR_INVALID_POINTER
        jmp     .rc
endp





;=============================================================================
; mem.size
; desc: returns size of memory block
; args: blockptr - pointer to memory block returned by mem.alloc
; ret: CF set on error, orherwise
;      eax = size of memory block
; error: ERR_INVALID_POINTER - blockptr isn't a valid block pointer
;        ERR_HEAP_CORRUPT    - you was overwriting memory outside allocated block
;        ERR_UNKNOWN
;        ERR_MODULE_NOT_INITIALIZED
;=============================================================================
proc GetMemSize, .blockptr
begin
        push    ebx

        ;is module initialized?
        cmp     [mem.initialized], 0
        je      .error_not_initialized

        ;was heap corruption detected?
        cmp     [mem.heap_corrupt], 0
        jne     .error_heap_corrupt

        ;EBX = pointer to heap block
        mov     ebx, [.blockptr]
        sub     ebx, sizeof.HEAPBLOCK

        ;check if pointer lies inside heap
        mov     eax, ebx
        sub     eax, [mem.heap.start]
        jb      .error_bad_pointer
        sub     eax, [mem.heap.size]
        jae     .error_bad_pointer

        ;check block
        cmp     [ebx + HEAPBLOCK.sign], 'HB'
        jne     .error_bad_pointer
        cmp     [ebx + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        jb      .error_bad_pointer
        cmp     [ebx + HEAPBLOCK.last], 1
        ja      .error_heap_corrupt

        ;return size
        mov     eax, [ebx + HEAPBLOCK.asize]
        cmp     eax, [ebx + HEAPBLOCK.size]
        ja      .error_heap_corrupt

.rnc:   clc
.r:     pop     ebx
        return

.rc:    stc
        pop     ebx
        return

.error_not_initialized:
        mov     eax, ERR_MODULE_NOT_INITIALIZED
        jmp     .rc

.error_heap_corrupt:
        int3
        mov     [mem.heap_corrupt], 1
        mov     eax, ERR_HEAP_CORRUPT
        jmp     .rc

.error_bad_pointer:
        mov     eax, ERR_INVALID_POINTER
        jmp     .rc
endp



;_______________________________________________________________________________________________


;===================================================================
; internal - for testing heap
proc __mem.test_heap
begin
        push    ebx edx

        ;is module initialized?
        cmp     [mem.initialized], 0
        je      .error_not_initialized

        ;was heap corruption detected?
        cmp     [mem.heap_corrupt], 0
        jne     .error_heap_corrupt

        mov     ebx, [mem.heap.start]
        mov     edx, 0

.process_block:

        ;check if block lies in heap
        mov     eax, ebx
        sub     eax, [mem.heap.start]
        jb      .error_heap_corrupt
        cmp     eax, [mem.heap.size]
        jae     .error_heap_corrupt

        ;check block
        cmp     [ebx + HEAPBLOCK.sign], 'HB'
        jne     .error_heap_corrupt
        cmp     [ebx + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        cmp     [ebx + HEAPBLOCK.prev], edx
        jne     .error_heap_corrupt

        ;if used, check 0 < asize <= size
        cmp     [ebx + HEAPBLOCK.used], 1
        ja      .error_heap_corrupt
        jb      @f
        mov     eax, [ebx + HEAPBLOCK.asize]
        test    eax, eax
        je      .error_heap_corrupt
        cmp     eax, [ebx + HEAPBLOCK.size]
        ja      .error_heap_corrupt
        @@:

        ;check if last block fills entire heap
        cmp     [ebx + HEAPBLOCK.last], 1
        ja      .error_heap_corrupt
        jb      @f
        lea     eax, [ebx + sizeof.HEAPBLOCK]
        add     eax, [ebx + HEAPBLOCK.size]
        sub     eax, [mem.heap.start]
        cmp     eax, [mem.heap.size]
        jne     .error_heap_corrupt
        @@:

        ;if this was last block, end
        cmp     [ebx + HEAPBLOCK.last], 1
        je      .done

        ;move to next block
        mov     edx, ebx
        add     ebx, [ebx + HEAPBLOCK.size]
        add     ebx, sizeof.HEAPBLOCK
        jmp     .process_block

.done:
        ;does last block fill entire heap?
        lea     eax, [ebx + sizeof.HEAPBLOCK]
        add     eax, [ebx + HEAPBLOCK.size]
        sub     eax, [mem.heap.start]
        cmp     eax, [mem.heap.size]
        jne     .error_heap_corrupt

.rnc:   clc
.r:     pop     edx ebx
        return

.rc:    stc
        jmp     .r

.error_not_initialized:
        mov     eax, ERR_MODULE_NOT_INITIALIZED
        jmp     .rc

.error_heap_corrupt:
        int3
        mov     [mem.heap_corrupt], 1
        mov     eax, ERR_HEAP_CORRUPT
        jmp     .rc
endp





;; @name mem.copy
;; @desc
;;    Copies contents block of memory of given size to another place.
;;    Supports overlapped copy (like "memmove" in C).
;; @arg dest
;;    pointer to destination place, to where the block will be copied
;; @arg src
;;    pointer to source place, from where the block will be copied
;; @arg size
;;    number of bytes to copy
;; @ret
;;    CF = 1 on error, otherwise
;;    EAX = dest
;; @err ERR_INVALID_POINTER
;;    some of pointers point to inaccessible area
;; @note Supports overlapped copy, eg. case when src < dest < src+size.
;; @note Does nothing when size=0
;; @note Memory management doesn't have to be initalized for this to work
proc mem.copy, .dest, .src, .size
begin
        push    ecx esi edi
        pushf

        mov     edi, [.dest]
        mov     esi, [.src]
        mov     ecx, [.size]

        ;just return when copying 0 bytes
        cmp     ecx, 0
        je      .done

        ;check if dest area is overlapping src (when src < dest < src+size)
        cmp     esi, edi
        je      .done ;if dest=src, we don't need to move
        ja      .not_overlapping
        lea     eax, [esi+ecx]
        cmp     edi, eax
        jae     .not_overlapping


        ;if it's overlapping
.overlapping:
        ;start moving from end of buffers
        add     esi, ecx
        add     edi, ecx

        ;classical unoptimized unalgined move
        std
        dec     esi
        dec     edi
        shr     ecx, 1
        jnc     @f
        movsb
@@:

        dec     esi
        dec     edi
        shr     ecx, 1
        jnc     @f
        movsw
@@:     jecxz   @f

        sub     esi, 2
        sub     edi, 2
        rep     movsd
@@:

        jmp     .done


.not_overlapping:

        ;classical unoptimized unalgined move
        cld
        shr     ecx, 1
        jnc     @f
        movsb
@@:     shr     ecx, 1
        jnc     @f
        movsw
@@:     jecxz   @f
        rep     movsd
@@:
        ;everything is okay
.done:  and     byte [esp],not 1     ;clear CF in stack
        mov     eax,[.dest]

.r:     popf
        pop     edi esi ecx
        return
endp


;============================================================================
;; @name mem.fill
;; @desc
;;    Fills block of memory with given dword pattern.
;; @arg dest
;;    Pointer to block which we will fill.
;; @arg size
;;    Size of block to fill.
;;    If size isn't multiple of 4, remaining bytes will be filled with according
;;    bytes of filler (little endian).
;; @arg value
;;    Dword value with which the block is filled.
;; @ret
;;    CF = 1 on error, otherwise
;;    EAX = dest
;; @err ERR_INVALID_POINTER
;;    dest points to inaccessible area
;; @note Does nothing when size=0
;; @note Memory management doesn't have to be initalized for this to work
proc mem.fill, .dest, .size, .value
begin
        push    ecx edi
        pushf
        cld

        mov     edi, [.dest]
        mov     ecx, [.size]
        mov     eax, [.value]

        ;just return if size=0
        cmp     ecx,0
        je      .done

        ;align filling pointer to dword
        test    edi, 1b
        jz      @f
        stosb
        ror     eax, 8
        dec     ecx
@@:     test    edi, 10b
        jz      @f
        stosw
        ror     eax, 16
        dec     ecx
        dec     ecx
@@:
        ;do the main fill
        push    ecx
        shr     ecx, 2
        rep     stosd
        pop     ecx

        ;fill rest
        shr     ecx, 1
        jnc     @f
        stosb
        ror     eax, 8
@@:     shr     ecx, 1
        jnc     @f
        stosw
@@:
        ;everything is okay
.done:  and     byte [esp],not 1  ;clear CF in stack
        mov     eax, [.dest]

.r:     popf
        pop     edi ecx
        return
endp




endmodule