
; a very basic implementation of malloc/realloc
; for porting fasmg to systems that do not have such API natively

struct MemoryHeader
	size dd ?		; total size of this block, the lowest bit set for blocks in use
	preceding_size dd ?	; total size of the block that precedes this one in memory (zero if this is an initial block of address range)
ends

struct FreeMemory
	header MemoryHeader
	right dd ?		; next free block in cyclic list
	left dd ?		; previous free block in cyclic list
ends

assert defined valloc
; in: ecx = requested minimum size
; out: eax - allocated block, ecx = allocated size, zero if failed
; preserves: ebx, esi, edi
; note:
;  valloc is called to request raw memory from the OS;
;  it is encouraged to allocate more memory than requested (even entire available memory),
;  the obtained memory is kept indefinitely in the pool for malloc
;  and should be released by OS automatically when the process ends;
;  if the OS does not do it automatically, additional list of the memory areas
;  may need to be maintained to release them before exit

malloc:
malloc_fixed:
malloc_growable:
; in: ecx = requested size
; out: eax - allocated block, ecx = allocated size, on error jumps to out_of_memory (does not return)
; preserves: ebx, esi, edi
	add	ecx,sizeof.MemoryHeader-1
	jc	out_of_memory
	and	ecx,(-1) shl 2
	add	ecx,1 shl 2
	jc	out_of_memory
	cmp	ecx,sizeof.FreeMemory
	jae	malloc_size_ok
	mov	ecx,sizeof.FreeMemory
    malloc_size_ok:
	mov	eax,[malloc_freelist]
	test	eax,eax
	jz	malloc_new
    find_fit:
	cmp	ecx,[eax+MemoryHeader.size]
	jbe	malloc_use_block
	mov	eax,[eax+FreeMemory.left]
	cmp	eax,[malloc_freelist]
	jne	find_fit
    malloc_new:
	push	ecx
	add	ecx,sizeof.MemoryHeader
	jc	out_of_memory
	call	valloc
	test	ecx,ecx
	jz	out_of_memory
	xor	edx,edx
	mov	[eax+MemoryHeader.preceding_size],edx
	pop	edx
	push	eax
	sub	ecx,edx
	cmp	ecx,sizeof.FreeMemory+sizeof.MemoryHeader
	jb	no_space_for_free_block
	mov	[eax+MemoryHeader.size],edx
	add	eax,edx
	mov	[eax+MemoryHeader.preceding_size],edx
	mov	edx,ecx
	sub	edx,sizeof.MemoryHeader
	dec	edx
	and	edx,(-1) shl 2
	add	edx,1 shl 2
	mov	[eax+MemoryHeader.size],edx
	mov	ecx,[malloc_freelist]
	jecxz	freelist_empty
	mov	[eax+FreeMemory.left],ecx
	mov	edx,eax
	xchg	edx,[ecx+FreeMemory.right]
	mov	[eax+FreeMemory.right],edx
	mov	[edx+FreeMemory.left],eax
	mov	edx,[eax+MemoryHeader.size]
	jmp	free_block_ready
    no_space_for_free_block:
	sub	ecx,sizeof.MemoryHeader
	add	edx,ecx
	mov	[eax+MemoryHeader.size],edx
	jmp	append_limiter
    freelist_empty:
	mov	[eax+FreeMemory.left],eax
	mov	[eax+FreeMemory.right],eax
    free_block_ready:
	mov	[malloc_freelist],eax
    append_limiter:
	add	eax,edx
	mov	[eax+MemoryHeader.preceding_size],edx
	mov	[eax+MemoryHeader.size],sizeof.MemoryHeader or 1	; cannot be freed
	pop	eax
    finish_malloc:
	mov	ecx,[eax+MemoryHeader.size]
	or	[eax+MemoryHeader.size],1
	add	eax,sizeof.MemoryHeader
	sub	ecx,sizeof.MemoryHeader
	retn
    malloc_use_block:
	mov	edx,[eax+MemoryHeader.size]
	sub	edx,ecx
	cmp	edx,sizeof.FreeMemory
	jb	use_whole_block
	mov	[eax+MemoryHeader.size],ecx
	mov	[eax+ecx+MemoryHeader.preceding_size],ecx
	add	ecx,eax
	mov	[malloc_freelist],ecx
	mov	[ecx+MemoryHeader.size],edx
	mov	[ecx+edx+MemoryHeader.preceding_size],edx
	mov	edx,[eax+FreeMemory.right]
	cmp	edx,eax
	je	update_free_singleton
	mov	[ecx+FreeMemory.right],edx
	mov	[edx+FreeMemory.left],ecx
	mov	edx,[eax+FreeMemory.left]
	mov	[ecx+FreeMemory.left],edx
	mov	[edx+FreeMemory.right],ecx
	jmp	finish_malloc
    update_free_singleton:
	mov	[ecx+FreeMemory.left],ecx
	mov	[ecx+FreeMemory.right],ecx
	jmp	finish_malloc
    use_whole_block:
	mov	edx,[eax+FreeMemory.right]
	cmp	edx,eax
	je	depleted_freelist
	mov	ecx,[eax+FreeMemory.left]
	mov	[ecx+FreeMemory.right],edx
	mov	[edx+FreeMemory.left],ecx
	mov	[malloc_freelist],edx
	jmp	finish_malloc
    depleted_freelist:
	and	[malloc_freelist],0
	jmp	finish_malloc

mfree:
; in: eax - memory block
; out: cf set on error
; preserves: ebx, esi, edi
; note: eax may have value 0 or -1, it should be treated as invalid input then
	test	eax,eax
	jz	mfree_error
	cmp	eax,-1
	je	mfree_error
	sub	eax,sizeof.MemoryHeader
	mov	ecx,[eax+MemoryHeader.size]
	btr	ecx,0
	jnc	mfree_error
	cmp	ecx,sizeof.FreeMemory
	jb	mfree_error
	cmp	[eax+ecx+MemoryHeader.preceding_size],ecx
	jne	mfree_error
	mov	[eax+MemoryHeader.size],ecx
	mov	edx,eax
	sub	edx,[eax+MemoryHeader.preceding_size]
	cmp	edx,eax
	je	no_preceding_block
	test	[edx+MemoryHeader.size],1
	jz	coalesce_with_preceding_block
    no_preceding_block:
	test	[eax+ecx+MemoryHeader.size],1
	jz	coalesce_with_following_block
	mov	ecx,[malloc_freelist]
	jecxz	mfree_init_freelist
	mov	edx,[ecx+FreeMemory.right]
	mov	[eax+FreeMemory.left],ecx
	mov	[edx+FreeMemory.left],eax
	mov	[eax+FreeMemory.right],edx
	mov	[ecx+FreeMemory.right],eax
    mfree_ok:
	mov	[malloc_freelist],eax
	clc
	retn
    mfree_init_freelist:
	mov	[eax+FreeMemory.left],eax
	mov	[eax+FreeMemory.right],eax
	jmp	mfree_ok
    mfree_error:
	stc
	retn
    coalesce_with_preceding_block:
	add	ecx,[edx+MemoryHeader.size]
	test	[edx+ecx+MemoryHeader.size],1
	jz	coalesce_on_both_ends
	mov	[edx+MemoryHeader.size],ecx
	mov	[edx+ecx+MemoryHeader.preceding_size],ecx
	clc
	retn
    coalesce_on_both_ends:
	lea	eax,[edx+ecx]
	add	ecx,[eax+MemoryHeader.size]
	mov	[edx+MemoryHeader.size],ecx
	mov	[edx+ecx+MemoryHeader.preceding_size],ecx
	mov	[malloc_freelist],edx
	mov	ecx,[eax+FreeMemory.left]
	mov	edx,[eax+FreeMemory.right]
	mov	[edx+FreeMemory.left],ecx
	mov	[ecx+FreeMemory.right],edx
	clc
	retn
    coalesce_with_following_block:
	push	ebx
	lea	ebx,[eax+ecx]
	add	ecx,[ebx+MemoryHeader.size]
	mov	[eax+MemoryHeader.size],ecx
	mov	[eax+ecx+MemoryHeader.preceding_size],ecx
	mov	ecx,[ebx+FreeMemory.left]
	mov	edx,[ebx+FreeMemory.right]
	mov	[ecx+FreeMemory.right],eax
	mov	[edx+FreeMemory.left],eax
	mov	ecx,[ebx+FreeMemory.left]
	mov	edx,[ebx+FreeMemory.right]
	mov	[eax+FreeMemory.left],ecx
	mov	[eax+FreeMemory.right],edx
	pop	ebx
	jmp	mfree_ok

realloc:
; in: eax - memory block, ecx = requested size
; out: eax - resized block, ecx = allocated size, on error jumps to out_of_memory (does not return)
; preserves: ebx, esi, edi
	add	ecx,sizeof.MemoryHeader-1
	jc	out_of_memory
	and	ecx,(-1) shl 2
	add	ecx,1 shl 2
	jc	out_of_memory
	sub	eax,sizeof.MemoryHeader
	mov	edx,[eax+MemoryHeader.size]
	and	edx,not 1
	cmp	ecx,edx
	jbe	realloc_retain
	test	[eax+edx+MemoryHeader.size],1
	jnz	realloc_and_copy
	add	edx,[eax+edx+MemoryHeader.size]
	cmp	ecx,edx
	ja	realloc_and_copy
	sub	edx,ecx
	cmp	edx,sizeof.FreeMemory
	jb	append_whole_block
	push	esi edi
	push	ecx edx
	lea	edi,[eax+ecx]
	xchg	ecx,[eax+MemoryHeader.size]
	and	ecx,not 1
	lea	esi,[eax+ecx]
	mov	ecx,[esi+FreeMemory.left]
	mov	edx,[esi+FreeMemory.right]
	mov	[edx+FreeMemory.left],edi
	mov	[ecx+FreeMemory.right],edi
	mov	ecx,[esi+FreeMemory.left]
	mov	edx,[esi+FreeMemory.right]
	mov	[edi+FreeMemory.left],ecx
	mov	[edi+FreeMemory.right],edx
	mov	[malloc_freelist],edi
	pop	edx ecx
	mov	[edi+MemoryHeader.size],edx
	mov	[edi+edx+MemoryHeader.preceding_size],edx
	mov	[edi+MemoryHeader.preceding_size],ecx
	pop	edi esi
	jmp	finish_malloc
    append_whole_block:
	add	edx,ecx
	mov	[eax+edx+MemoryHeader.preceding_size],edx
	xchg	edx,[eax+MemoryHeader.size]
	and	edx,not 1
	add	edx,eax
	mov	ecx,[edx+FreeMemory.left]
	cmp	ecx,edx
	je	depleted_freelist
	mov	edx,[edx+FreeMemory.right]
	mov	[ecx+FreeMemory.right],edx
	mov	[edx+FreeMemory.left],ecx
	mov	[malloc_freelist],ecx
	jmp	finish_malloc
    realloc_retain:
	and	[eax+MemoryHeader.size],not 1
	jmp	finish_malloc
    realloc_and_copy:
	push	esi edi
	lea	esi,[eax+sizeof.MemoryHeader]
	call	malloc_growable
	push	eax ecx
	mov	edi,eax
	mov	eax,esi
	mov	ecx,[esi-sizeof.MemoryHeader+MemoryHeader.size]
	shr	ecx,2
	rep	movsd
	call	mfree
	pop	ecx eax
	pop	edi esi
	retn

if used mcheck

    mcheck:
	pushf
	push	eax ebx ecx edx
	mov	eax,[malloc_freelist]
	test	eax,eax
	jz	integrity_verified
      verify_freelist:
	mov	ebx,eax
      verify_preceding_blocks:
	mov	ecx,[ebx+MemoryHeader.preceding_size]
	jecxz	preceding_blocks_ok
	sub	ebx,ecx
	mov	edx,[ebx+MemoryHeader.size]
	and	edx,not 1
	cmp	ecx,edx
	je	verify_preceding_blocks
	jmp	internal_error
      preceding_blocks_ok:
	mov	ebx,eax
      verify_following_blocks:
	mov	ecx,[ebx+MemoryHeader.size]
	and	ecx,not 1
	cmp	ecx,sizeof.MemoryHeader
	je	following_blocks_ok
	add	ebx,ecx
	cmp	ecx,[ebx+MemoryHeader.preceding_size]
	je	verify_following_blocks
	jmp	internal_error
      following_blocks_ok:
	mov	edx,[eax+FreeMemory.right]
	cmp	eax,[edx+FreeMemory.left]
	je	verify_next
	jmp	internal_error
      verify_next:
	mov	eax,edx
	cmp	eax,[malloc_freelist]
	jne	verify_freelist
      integrity_verified:
	pop	edx ecx ebx eax
	popf
	retn

end if
