

; --------------------------------------------------------
;		Dynamische Speicherverwaltung
; --------------------------------------------------------

mem_start	data	2
mem_handles_end	data	2
mem_data_start	data	2
mem_end		data	2
mem_malloc_ptr	data	2

mem_free	data	2
mem_compact_cnt	data	1
mem_error_handler data	2


mem_hl_address	equ	0	; offset in handle: pointer to data
mem_hl_size	equ	2	; offset in handle: data size
mem_hl_usage	equ	1	; offset in handle: referenz counter
				; unbenutzte Handles haben cnt = 0
mem_handle_size	equ	5

lo		equ	0	; offset of low byte in word
hi		equ	1	; offset of high byte in word



; --------------------------------------------------------
;
; in:	a = error no.
; out:	--
; mod:	does not return

mem_abort_with_error:
	ld	(errno),a
	ld	hl,(mem_error_exit)	; unset => reset
	jp	(hl)			



; ---------------------------------------
; Set error handler
;
; in:	hl = new handler
; out:	--
; mod:	--

mem_set_error_handler:
	ld	(mem_error_handler),hl
	ret
	


; ---------------------------------------
; Get error handler
;
; in:	--
; out:	hl = current handler
; mod:	--

mem_get_error_handler:
	ld	hl,(mem_error_handler)
	ret
	


; --------------------------------------------------------
; Initialize Memory Handler
;
; in:	hl -> first usable byte
;	de -> behind last usable byte
; out:	--
; mod:	af, de, hl

mem_init_from_hl_to_de:
	ld	(mem_start),hl
	ld	(mem_handles_end),hl
	ld	(mem_malloc_ptr),hl
	ld	(mem_data_start),de
	ld	(mem_end),de
	ex	hl,de
	and	a
	sbc	hl,de
	ld	(mem_free),hl		
	ret
	


; --------------------------------------------------------
; Get total amount of free memory
;
; in:	--
; out:	bc = free memory size (total)
; mod:	bc

mem_get_free:
	ld	bc,(mem_free)
	ret
	


; --------------------------------------------------------
; Get maximum size of memory request 
; which will not trigger a garbage collection
;
; in:	--
; out:	bc = free memory size (immediately available)
; mod:	af, bc

mem_get_max:
	push	hl
	ld	hl,(mem_data_start)
	ld	bc,mem_handle_size
	and	a
	sbc	hl,bc
	ld	bc,(mem_handles_end)
	sbc	hl,bc
	ld	bc,hl
	pop	hl
	ret	nc
	ld	bc,0
	ret
	


; --------------------------------------------------------
; Validate handle.
; 
; in:	hl -> handle
; out:	iy -> handle
; mod:	af, iy

mem_validate_hl:		
	push	hl
	pop	iy

mem_validate_iy:
	push	bc
	push	de
	push	hl

	push	iy
	pop	de

	ld	hl,(mem_handles_end)
	scf
	sbc	hl,de		; mem_handles_end -1 -handle
	jr	c,mvx		; mem_handles_end -1 -handle < 0 <=> handle >= mem_handles_end

	ld	hl,(mem_start)
	ex	hl,de
	sbc	hl,de		; handle - mem_start
	jr	c,mvx		; handle - mem_start < 0 <=> handle < mem_start

; hl = handle - mem_start 
; => hl = n * 5
xxx	ld	b,12		; Zähler für Abbruchkriterium
	ld	de,5<<12	; hl = 5<<12	4k handles = 20k ram nur für handles, 
	;and	a		; nc		das dürfte max out sein
		
mv2	sbc	hl,de
	jr	nc,mv3		; n*5(hl) > 5<<i(de)	=>  abziehen war ok
	add	hl,de		; n*5(hl) < 5<<i(de)	=>  abziehen war nicht ok => undo 
mv3	jr	z,mv4		; n*5(hl) == 5<<i(de)	=>  passt!
	add	hl,hl		; n*5<<j++
	djnz	mv2		; noch besteht Hoffnung...
	jr	mvx		; Division durch 5 hat Rest => hl nicht aligniert!

; prüfe addresse, size und count:
mv4	and	a
	cp	(iy+mem_hl_usage)
	jr	z,mvx

	ld	h,(iy+mem_hl_size+hi)
	ld	l,(iy+mem_hl_size+lo)
	ld	a,h
	rlca
	jr	c,mvx		; size < 0

	ld	d,(iy+mem_hl_address+hi)
	ld	e,(iy+mem_hl_address+lo)

	add	hl,de		; de = block_start; hl = block_end

	ld	bc,(mem_end)	
	inc	bc
	sbc	hl,bc		; block_end -1 -mem_end
	jr	nc,mvx		; block_end -1 -mem_end < 0  <=> block_end > mem_end

	ld	hl,(mem_data_start)	
	and	a
	sbc	hl,de		; mem_data_start -blockstart
	jr	c,mvx		; mem_data_start -blockstart < 0 <=> mem_data_start < blockstart

	pop	hl
	pop	de
	pop	bc
	ret

mvx	ld	a,error_memvalidate
	jp	mem_abort_with_error



; --------------------------------------------------------
; Increment usage counter for handle.
; call it after duplicating the reference pointer.
;
; in:	hl -> handle
; out:	iy -> handle
; mod:	f, iy

mem_incr_usage_for_hl:	
	push	hl
	pop	iy
	
mem_incr_usage_for_iy:	
	inc	(iy+mem_hl_usage)
	ret	nz
	ld	a,error_memincr
	jp	mem_abort_with_error
	


; --------------------------------------------------------
; Decrement usage counter.
; Call this after destroying a reference pointer.
; May release the handle and data.
; 
; in:	hl -> handle
; out:	iy -> handle
; mod:	f, iy

mem_free_hl:
mem_decr_usage_for_hl:	
	push	hl
	pop	iy

mem_free_iy:
mem_decr_usage_for_iy:			; TODO: update mem_free
	dec	(iy+mem_hl_usage)
	ret	p
	push	af
	ld	a,(iy+mem_hl_usage)
	inc	a
	jr	z,mdux
	pop	af
	ret
mdux	pop	af
	ld	a,error_memdecr
	jp	mem_abort_with_error
	


; --------------------------------------------------------
; Get address of data for handle.
;
; in:	hl -> handle
; out:	iy -> handle
; mod:	hl, iy

mem_get_address_for_hl:
	push	hl
	pop	iy

mem_get_address_for_iy:
	ld	h,(iy+mem_hl_address+hi)
	ld	l,(iy+mem_hl_address+hi)
	ret
	
	

; --------------------------------------------------------
; Get size of data for handle.
;
; in:	hl -> handle
; out:	iy -> handle
;	bc = size
; mod:	bc, iy

mem_get_size_for_hl:
	push	hl
	pop	iy

mem_get_size_for_iy:
	ld	b,(iy+mem_hl_size+hi)
	ld	c,(iy+mem_hl_size+hi)
	ret


; --------------------------------------------------------
; Get usage count for handle.
;
; in:	hl -> handle
; out:	iy -> handle
;	a = usage count. 0=free
; mod:	a, iy

mem_get_usage_count_for_hl:
	push	hl
	pop	iy

mem_get_usage_count_for_iy:
	ld	a,(iy+mem_hl_size+hi)
	ret



; --------------------------------------------------------
; Find handle for address. (slow)
;
; in:	hl -> start of/inside data block
; out:	iy -> handle
;	hl -> handle
;	F: Z = not found, hi=iy=0; NZ = found, hl=iy -> handle
; mod:	af hl iy

mem_get_handle_for_address:
	push	bc
	push	de
	
	ld	iy,(mem_handles_end)	; iy -> current handle

	ld	a,(mem_start+hi)
	cpl
	ld	d,a
	ld	a,(mem_start+lo)
	cpl
	ld	e,a			; de = -1 -mem_start
	jr	mgh1

mgh3	add	hl,bc
mgh1	ld	bc,-5
mgh2	add	iy,bc	

mgh0	push	iy			; start of handles reached?
	add	iy,de			; handle - mem_start -1
	pop	iy
	jr	nc,mghx			; handle - mem_start -1 <= 0 <=> handle < mem_start 
	
	ld	a,(iy+mem_hl_usage)	; a = handle.usage
	and	a
	jr	z,mgh2			; das handle ist nicht belegt

	ld	b,(iy+mem_hl_address+hi) ; bc = handle.data.start
	ld	c,(iy+mem_hl_address+lo)
	
	;and	a
	sbc	hl,bc			; my address - handle address
	jr	c,mgh3			; my address - handle address < 0 <=> my address < handle address
	add	hl,bc			; undo

	ld	a,c
	add	(iy+mem_hl_size+lo)
	ld	c,a
	ld	a,b
	adc	(iy+mem_hl_size+hi)
	ld	b,a			; bc = handle.data.end
	
	;and	a
	sbc	hl,bc			; my.address - handle.data.end
	jr	nc,mgh3			; my.address - handle.data.end >= 0 <=> my.address >= handle.data.end
	;add	hl,bc			; undo
	
; alles passt! das ist es!

	push	iy
	pop	hl
	
	pop	de
	pop	bc
	ret				; ret nz  =>  hl = iy -> handle

; nicht gefunden:

mghx	sub	a			; z
	ld	h,a
	ld	l,a			; hl = 0
	push	hl
	pop	iy			; iy = 0

	pop	de
	pop	bc
	ret				; ret z  =>  hl = iy = 0

	

; --------------------------------------------------------
; Duplicate data and return new handle for it.
; 
;
; in:	hl -> handle
; out:	iy -> new handle
;	hl -> new handle
; mod:	af, hl, iy

mem_dup_hl:		
	push	hl
	pop	iy

mem_dup_iy:
	push	bc
	push	de

	ld	b,(iy+mem_hl_size+hi)	; bc = size
	ld	c,(iy+mem_hl_size+hi)

	push	iy
	call	mem_alloc_bc_bytes	; -> hl iy
	ex	hl,de			; de -> new block = dest
	pop	hl			; hl -> old block = source

	push	de	; -> new block
	ld	a,b
	or	c
	jr	z,$+4
	ldir
	pop	hl	; -> new block

	pop	de
	pop	bc
	ret



; --------------------------------------------------------
; Allocate memory.
;
; triggers a garbage collection if requested size is not immediately available.
; jumps to oomem handler, if after garbage collection is still not enough memory free.
;
; in:	bc = requested size
; out:	iy -> handle
;	hl -> data
; mod:	af, hl, iy

mem_alloc_bc_bytes:	
	bit	7,b
	jr	nz,max
	
	push	de

	call	up_new_handle		; hl -> handle
	push	hl
	pop	iy

	ld	hl,(mem_data_start)
	ld	de,(mem_handles_end)
	and	a
	sbc	hl,de		; hl = free
	sbc	hl,bc		; hl = remaining free
	jr	c,ma1		; not available => compact memory
ma2	add	hl,de		; hl = hl-de-bc+de = hl-bc = old mem_data_start - req_size
				; hl = new handle.address = new mem_data_start
		
	ld	(mem_data_start),hl

	ld	(iy+mem_hl_address+hi),h
	ld	(iy+mem_hl_address+lo),l
	ld	(iy+mem_hl_size+hi),b
	ld	(iy+mem_hl_size+lo),c
	ld	(iy+mem_hl_usage),1
	
	pop	de
	ret

; internal error: size negative => abort with error

max:	ld	a,error_mallocsizeneg
	jp	mem_abort_with_error

; memory not available => try garbage collection

ma1	call	mem_compact

	push	hl
	push	bc
	ld	hl,(mem_data_start)
	ld	bc,(mem_handles_end)
	and	a
	sbc	hl,bc
	pop	bc
	sbc	hl,bc
	pop	hl
	jr	nc,ma2		; retry. should not fail again

mem_oomem:
	ld	a,error_oomem
	jp	mem_abort_with_error



; --------------------------------------------------------
; Find a new free handle
; 
; in:	--
; out:	hl -> handle
; mod:	af, hl

up_new_handle:
	push	de
	push	bc
	
nh0	ld	hl,(mem_handles_end)
	ld	de,(mem_malloc_ptr)
	ld	bc,mem_handle_size	; bc=5

	add	hl,bc
	dec	hl		
	ex	hl,de			; de = mem_handles_end.usage
	add	hl,bc
	dec	hl			; hl = mem_malloc_ptr.usage

	jr	nh4
	
nh3	ld	a,(hl)
	and	a
	jr	z,nh2			; free slot found

; slot is in use	
	add	hl,bc			; advance to next slot
	
nh4	ld	a,l
	cp	e
	jr	nz,nh3			; not at end
	ld	a,h
	cp	d
	jr	nz,nh3			; not at end
	
; out of handles

	ld	de,(mem_start)
	ld	a,(mem_malloc_ptr+lo)
	cp	e
	jr	nz,nh5	
	ld	a,(mem_malloc_ptr+hi)
	cp	d
nh5	ld	(mem_malloc_ptr),de
	jr	nz,nh0			; retry search from start
	
; all handles used up => alloc some more	
	call	up_more_handles		; if it returns then we got new handles
	
; free slot found:
nh2	inc	hl			; hl -> next slot
	ld	(mem_malloc_ptr),hl
	sbc	hl,bc			; -handle_size 	

	pop	bc
	pop	de
	ret				; hl -> handle
	


; --------------------------------------------------------
; Allocate some handles
;
; in:	--
; out:	--
; mod:	af

up_more_handles:
	push	hl
	push	de
	push	bc

	ld	hl,(mem_handles_end)
	ld	de,8*mem_handle_size	; 8 new handles
	add	hl,de
	ex	hl,de			; de = new handles_end

	ld	hl,(mem_data_start)
	sbc	hl,de			; size of new free memory gap
	jr	c,mmh1			; not immediately available => try compact

mmh3	ex	hl,de			; hl = new handles_end
	ld	(mem_handles_end),hl
	ld	de,mem_handle_size	; d=0; de = c = mem_handle_size
	add	hl,de			
	dec	hl			; hl -> new_handle[0].usage
	ld	b,8			; 8 new handles
	
mmh2	ld	(hl),d			; clear usage count => free
	add	hl,de			; next
	djnz	mmh2

	pop	bc
	pop	de
	pop	hl
	ret

; less than 16 bytes available => try garbage collection
mmh1	call	mem_compact
	ld	hl,(mem_data_start)
	sbc	hl,de
	jr	nc,mmh3			; now available	

	jr	mem_oomem





#if 0
; --------------------------------------------------------
; Garbage Collection
;
; in:	--
; out:	--
; mod:	af

mem_gap_end	equ	mem_start_data
mem_gap_start	data	2

mem_compact:
	mem_gap_start = mem_end
	mem_gap_end = mem_end
	
loop1:	current_highest_addr = mem_gap_start
	current_highest_handle = 0	
	current_handle = mem_handles_end

loop2:	current_handle--
	if current_handle == mem_start then exit loop2
	if current_handle.usage == 0 then loop2
	if current_handle.address > mem_gap_start then loop2
	if current_handle.address < current_highest_addr then loop2
	
	current_highest_handle = current_handle
	current_highest_addr = current_highest_handle.address
	loop2
	
	if current_highest_handle == 0 then exit loop1
	
	mem_gap_start = current_highest_handle.address = current_highest_addr
	mem_gap_end -= current_highest_handle.size
	current_highest_handle.address = mem_gap_end
	copy current_highest_handle.size bytes from mem_gap_start to mem_gap_end
	loop1
	
	mem_start_data = mem_gap_end
	mem_compact_cnt += 1

	fertig!
#endif





; --------------------------------------------------------
; Garbage Collection
;
; in:	--
; out:	--
; mod:	af























