TITLE	LALLOC - Local memory allocator

include kernel.inc

.errnz	 la_prev		; This code assumes la_prev = 0

externW pLocalHeap

DataBegin

externB Kernel_flags

DataEnd

sBegin	CODE
assumes CS,CODE

externNP lalign 	; LINTERF.ASM
externNP lnotify	; LINTERF.ASM
externNP lcompact	; LCOMPACT.ASM

if KDEBUG
externNP CheckLAllocBreak   ; LINTERF.ASM
endif

;-----------------------------------------------------------------------;
; ljoin									;
; 									;
; Join two blocks together, by removing one.				;
; 									;
; Arguments:								;
;	BX = address of block to remove					;
;	SI = address of block that points to the one to remove		;
; 									;
; Returns:								;
;	BX = address of block following [SI], after join		;
;	Updated hi_count field in the local arena info structure	;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
; 									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Mon March 9, 1987  -by-  Bob Gunderson (bobgu)			;
; Added free list stuff.						;
;									;
;  Tue Oct 14, 1986 05:20:56p  -by-  David N. Weise   [davidw]          ;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	ljoin,<PUBLIC,NEAR>
cBegin nogen

    IncLocalStat    ls_ljoin

	push	di
	dec	[di].hi_count		; remove free block from arena
	mov	di,bx			; save ptr to block removing
	mov	bx,[bx].la_next		; Get address of block after
	and	[bx].la_prev,LA_ALIGN	; one we are removing.
	or	[bx].la_prev,si		; Change it's back link
	mov	[si].la_next,bx		; and change the forward link

; If it is free, remove the block at [DI] from the free list

	test	[di].la_prev,LA_BUSY
	jnz	join1
	xchg	bx,di
	call	lfreedelete		; delete from free list
	xchg	bx,di
join1:

; if the block at [SI] is free, set its new larger free block
; size.  If not free, then block at [DI] is about to become part
; of the previous busy block.

	test	[si].la_prev,LA_BUSY	; is it busy?
	jnz	joinexit		; yes

	push	ax
	mov	ax,bx
	sub	ax,si			; length of new free block
	mov	[si].la_size,ax
	pop	ax

if KDEBUG

; Fill new free block with DBGFILL_ALLOC

	xchg	si,bx
	call	lfillCC
	xchg	si,bx
endif

joinexit:
	pop	di
	ret
cEnd nogen


;-----------------------------------------------------------------------;
; lrepsetup                                                             ;
; 									;
; Sets up for a block store or move of words.				;
; 									;
; Arguments:								;
;	CX = #bytes							;
; 									;
; Returns:								;
;	CX = #words							;
;	ES = DS								;
;	DF = 0								;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
; 									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Tue Oct 14, 1986 05:23:25p  -by-  David N. Weise   [davidw]          ;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	lrepsetup,<PUBLIC,NEAR>
cBegin nogen
	shr	cx,1
	push	ds
	pop	es
	cld
	ret
cEnd nogen


;-----------------------------------------------------------------------;
; lzero									;
; 									;
; Fills a block with zeros.						;
; 									;
; Arguments:								;
;	BX = address of last word +1 to zero				;
;	CX = address of first word to zero				;
; 									;
; Returns:								;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
;	CX,ES								;
; 									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Tue Oct 14, 1986 05:25:30p  -by-  David N. Weise   [davidw]          ;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	lzero,<PUBLIC,NEAR>
cBegin nogen
	push	di
	mov	di,cx		; DI = destination
	sub	cx,bx		; CX = - #bytes
	jae	zero1		; Do nothing if BX less than or equal to CX
	push	ax
	neg	cx
	xor	ax,ax
	call	lrepsetup	; Setup for stosw
	rep	stosw		; Zero it out
	pop	ax
zero1:
	pop	di
	ret
cEnd nogen


if KDEBUG
;-----------------------------------------------------------------------;
; lallocfill                                                            ;
; 									;
; Fills a block with DBGFILL_ALLOC                                      ;
; 									;
; Arguments:								;
;	BX = address of last word +1 to zero				;
;	CX = address of first word to zero				;
; 									;
; Registers Destroyed:							;
;	CX,ES								;
; 									;
;-----------------------------------------------------------------------;

cProc   lallocfill,<PUBLIC,NEAR>
cBegin  nogen
	push	di
	mov	di,cx		; DI = destination
	sub	cx,bx		; CX = - #bytes
        jae     @F              ; Do nothing if BX less than or equal to CX
	push	ax
	neg	cx
        mov     ax,(DBGFILL_ALLOC or (DBGFILL_ALLOC shl 8))
	call	lrepsetup	; Setup for stosw
	rep	stosw		; Zero it out
	pop	ax
@@:
	pop	di
	ret
cEnd    nogen

endif   ;KDEBUG

;-----------------------------------------------------------------------;
; lalloc								;
; 									;
; Actually allocates a local object.  Called from within the local	;
; memory manager's critical section.					;
; 									;
; Arguments:								;
;	AX = allocations flags						;
;	BX = #bytes							;
;	DI = address of local arena information structure		;
; 									;
; Returns:								;
;	AX = data address of block allocated or NULL			;
;	DX = allocation flags or size of largest free block		;
;	     if AX = 0							;
;	ZF = 1 if AX = 0						;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
; 									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Mon March 9, 1986  -by- Bob Gunderson (bobgu)			;
; Changed the search algorithm to use the free list and added calls	;
; to add/remove blocks from the free list				;
;									;
;  Tue Oct 14, 1986 05:27:40p  -by-  David N. Weise   [davidw]          ;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	lalloc,<PUBLIC,NEAR>
cBegin nogen

if KDEBUG
        call    CheckLAllocBreak
        jnc     @F
        xor     ax,ax
        ret
@@:
endif

	push	si
	push	ax
	push	bx
	test	al,LA_MOVEABLE		; Moveable?
;	jnz	scanbwd 		; Yes, search backwards
	jz	@F
	jmp	scanbwd
@@:
    IncLocalStat    ls_falloc
	add	bx,la_fixedsize		; Room for fixed header
	call	lalign			; Get requested size in DX
	mov	bx,[di].hi_first	; No, start with first entry

; use free list to find a block to allocate
afwdloop:
	mov	ax,[bx].la_free_next	; get next free block
	cmp	ax,bx			; are we at end of arena ?
	jz	afwdcmp 		; yes try to compact....

    IncLocalStat    ls_fexamine
	mov	bx,ax
	cmp	dx,[bx].la_size 	; is it big enough?
	ja	afwdloop		; no, loop for next

; Here when we have a block big enough.
;   BX = address of block
;   DX = requested size, including fixed header

afwdfound:

if KDEBUG

; verify the block is filled with DBGFILL_FREE

	call	lcheckCC
	jz	afwdfound1		; all OK
        kerror  ERR_LMEM,<LocalAlloc: Local free memory overwritten>
afwdfound1:

endif

    IncLocalStat    ls_ffound
	mov	ax,la_fixedsize
	mov	cx,[bx].la_next 	; mov	cx,[bx].la_next
	sub	cx,LA_MINBLOCKSIZE	; CX = as low as we will go for free
	mov	si,bx			; Calculate address of new block
	add	si,dx			; (current block + requested size)
	cmp	si,cx			; Is it past the current next block
;;;;;;; jae	aexit1			; yes, use the whole block
	jb	afwdfound2
	jmp	aexit1
afwdfound2:

; Here with two blocks.
;   BX = address of existing block
;   SI = address of new block to add to arena as free space

    IncLocalStat    ls_ffoundne
	push	bx
	jmp	aexit


afwdcmp:
    IncLocalStat    ls_fcompact
	call	lcompact		; End of arena.	 Try compacting.
	cmp	ax,dx			; Room for requested size?
	jae	afwdfound		; Yes, exit search loop
afail:					; No, fail.
	push	ax			; Remember size of largest free block
	xor	bx,bx
	.errnz	LN_OUTOFMEM		; Notify client procedure
	xchg	ax,bx			; BX = size of largest free block
	mov	cx,dx			; CX = size we are looking for
	call	lnotify
	pop	dx			; DX = size of largest free block
	pop	bx			; BX = requested size
	pop	ax			; AX = flags
	pop	si			; Restore SI
;	jnz	lalloc			; Try again if notify procedure said to
	jz	@F
	jmp	lalloc
@@:
    IncLocalStat    ls_fail
	xor	ax,ax			; O.W. return zero, with Z flag set
	ret


scanbwd:
    IncLocalStat    ls_malloc
	add	bx,SIZE LocalArena	; Room for moveable header
	call	lalign			; Get requested size in DX
	mov	bx,[di].hi_last

; use free chain to find a block to aloocate

abwdloop:
    IncLocalStat    ls_mexamine
	mov	ax,[bx].la_free_prev	; previous free block
	cmp	ax,bx			; are we at beginning of arena ?
	jz	abwdcmp 		; yes try to compact....

	mov	bx,ax
	cmp	dx,[bx].la_size 	; Room for requested size?
	ja	abwdloop		; no, loop to previous free block
	jmps	abwdfound		; yes, alloocate the memory

; Beginning of arena.  Try compacting.	If that fails then too bad

abwdcmp:
    IncLocalStat    ls_mcompact
	call	lcompact
	cmp	ax,dx			; Room for requested size?
	jb	afail			; No, fail
	mov	si,ax			; Yes, get free size in SI

abwdfound:

if KDEBUG

; verify the block is filled with DBGFILL_FREE

	call	lcheckCC
	jz	abwdfound1		; all OK
        kerror  ERR_LMEM,<LocalAlloc: Local free memory overwritten>
abwdfound1:
endif

    IncLocalStat    ls_mfound
	mov	ax,SIZE LocalArena
	mov	si,[bx].la_size 	; size of block found
	sub	si,dx			; SI = size of free block = total size
					; less requested size (includes header)
	mov	cx,si			; save what's left over
	add	si,bx			; SI = address of new block
	cmp	cx,LA_MINBLOCKSIZE	; enough room for another?
	jb	aexit1			; no, use entire block
	push	si

    IncLocalStat    ls_mfoundne

; Here with two blocks, in following order
;   BX = address of existing free block to make smaller
;   SI = address of new block to add to arena as busy
;   CX = address of block after new block

aexit:
	mov	cx,si
	xchg	[bx].la_next,cx
	mov	[si].la_prev,bx
	mov	[si].la_next,cx

; Here with allocated block
;   BX = address of found block
;   SI = address of new block after found block
;   CX = address of block after new block

	xchg	si,cx			; SI = block after new block
	and	[si].la_prev,LA_ALIGN	; CX = new block
	or	[si].la_prev,cx		; Point to new block
	inc	[di].hi_count		; We added an arena entry
	mov	si,bx			; SI = previous free block
	mov	bx,cx			; BX = new block after found block
	call	lfreeadd		; add this new block to free list
	sub	bx,si
	mov	[si].la_size,bx 	; set new free block size
	pop	bx			; BX = block we are allocating

aexit1:
	call	lfreedelete		; remove this block from free list
	add	ax,bx
	or	byte ptr [bx].la_prev,LA_BUSY	; Mark block as busy
	pop	dx			; Flush requested size
	pop	dx			; Restore flags
if KDEBUG
        test    dl,LA_ZEROINIT
        jnz     @F
        mov     cx,ax
        mov     bx,[bx].la_next
        call    lallocfill
        jmp     short aexit2
@@:
endif
	test	dl,LA_ZEROINIT		; Want it zeroed?
	jz	aexit2			; No, all done
	mov	cx,ax			; Yes, CX = 1st word to zero
	mov	bx,[bx].la_next		; BX = last word+1 to zero
	call	lzero			; Zero them
aexit2:
	pop	si
	or	ax,ax			; Return AX points to client portion
	ret				; of block allocated or zero
cEnd nogen


;-----------------------------------------------------------------------;
; lfree                                                                 ;
; 									;
; Marks a block as free, coalescing it with any free blocks before	;
; or after it.								;
; 									;
; Arguments:								;
;	BX = block to mark as free.					;
;	DI = address of local arena information structure		;
; 									;
; Returns:								;
;	SI = 0 if freed a fixed block.  				;
;	SI = handle table entry, for moveable blocks.			;
;	ZF =1 if SI = 0							;
;	Updated hi_count field in local arena information structure	;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
; 									;
; Calls:								;
; 									;
; History:								;
;									;
;  Mon March 9, 1987  -by- Bob Gunderson (bobgu)			;
; Freed blocks are placed back on the free list 			;
;									;
;  Tue Oct 14, 1986 05:31:52p  -by-  David N. Weise   [davidw]          ;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	lfree,<PUBLIC,NEAR>
cBegin nogen
	mov	si,bx
	or	si,si
	jz	free4
	push	dx

	push	[bx].la_handle		; push possible handle

; Add block to free list

	push	si
	xor	si,si
	call	lfreeadd		; add this block to free list
	pop	si

; Clear any existing LA_BUSY and LA_MOVEABLE bits

	mov	dx,LA_BUSY + LA_MOVEABLE
	and	dx,[bx].la_prev
	xor	[bx].la_prev,dx
	and	dl,LA_MOVEABLE		; Moveable?
	pop	dx			; restore handle
	jnz	free1			; Yes, return handle in DX
	xor	dx,dx			; No, return 0 in DX
free1:
	mov	si,[bx].la_next		; SI = next block
	test	byte ptr [si].la_prev,LA_BUSY	; Is it free?
	jnz	free2			; No, continue
	xchg	bx,si
	call	ljoin			; Yes, coelesce with block in BX
	mov	bx,si
free2:
	mov	si,[bx].la_prev		; SI = previous block
	test	byte ptr [si].la_prev,LA_BUSY	; Is it free?
	jnz	free3			; No, continue
	call	ljoin			; Yes, coelesce with block in BX
free3:
	mov	si,dx			; Return 0 or handle in BX
	pop	dx			; restore DX
free4:
	or	si,si			; Set Z flag if SI = zero
	ret
cEnd nogen



;-----------------------------------------------------------------------;
; lfreeadd								;
;									;
; Links a block onto the free block chain. This routine assumes that	;
; the block to add already has the la_next and la_prev fields set	;
; to their proper values.  An extended free block header is written	;
; into the block.							;
;									;
; Arguments:								;
;	BX = Address of block to add to free list			;
;	SI = 0 to search for insertion point, else contins the address	;
;	       of the previous free block.				;
;									;
; Returns:								;
;									;
; Error Returns:							;
;									;
; Registers Preserved:							;
;	All are preserved						;
;									;
; Registers Destroyed:							;
;									;
; Calls:								;
;									;
; History:								;
;									;
;  Mon March 9, 1987   -by-  Bob Gunderson (bobgu)			;
;Initail version							;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	lfreeadd,<PUBLIC,NEAR>
cBegin nogen
	push	si
	push	di
	push	ax
	push	cx

	or	si,si			; need to search for insertion point?
	jnz	lfa1			; no

; We first need to find the previous free block so that we can insert this
; block into the proper place.

	mov	si,ds:pLocalHeap	; get local arena info block address

if KDEBUG

; Range check the insertion point

	cmp	bx,[si].hi_first
	jb	lfadie
	cmp	bx,[si].hi_last
	jb	lfaok
lfadie:
	kerror	ERR_LMEM,<lfreeadd : Invalid local heap>
lfaok:
endif

	mov	si,[si].hi_first    ; get first block address (free list header)
lfaloop:
	mov	ax,[si].la_free_next ; get address of next free block
	cmp	bx,ax		    ; will next block be past new block?
	jb	lfa1		    ; yes, DI contains block to insert AFTER
	mov	si,ax
	jmp	lfaloop 	    ; loop on next block

; At this point, BX = block to insert, SI = block to insert after

lfa1:
	mov	di,[si].la_free_next	; get next free block
	mov	[bx].la_free_next,di	;
	mov	[si].la_free_next,bx	; set new next block

	mov	[bx].la_free_prev,si	; set new previous block
	mov	[di].la_free_prev,bx	; ditto

	mov	ax,[bx].la_next 	; next block (not necessarily free)
	sub	ax,bx
	mov	[bx].la_size,ax 	; set the size of this block

if KDEBUG

; now fill the new free block with DBGFILL_ALLOC

	call	lfillCC
endif

	pop	cx
	pop	ax
	pop	di
	pop	si
	ret
cEnd nogen



;-----------------------------------------------------------------------;
; lfreedelete								;
;									;
; Removes a specified block from the free list.  This routine assums	;
; that the specified block is indeed on the free list			;
;									;
; Arguments:								;
;	BX = Address of block to remove from the free list		;
;									;
; Returns:								;
;									;
; Error Returns:							;
;									;
; Registers Preserved:							;
;	All are preserved						;
;									;
; Registers Destroyed:							;
;									;
; Calls:								;
;									;
; History:								;
;									;
;  Mon March 9, 1987   -by-  Bob Gunderson (bobgu)			;
;Initail version							;
;-----------------------------------------------------------------------;

	assumes	ds, nothing
	assumes	es, nothing

cProc	lfreedelete,<PUBLIC,NEAR>
cBegin nogen
	push	di
	push	si

	mov	di,[bx].la_free_prev
	mov	si,[bx].la_free_next

	mov	[di].la_free_next,si
	mov	[si].la_free_prev,di

	pop	si
	pop	di
	ret
cEnd nogen



;-----------------------------------------------------------------------;
; lfillCC								;
;									;
; Fills all the bytes in the specified block with DBGFILL_FREE          ;
;									;
; Arguments:								;
;	BX = Address of block to fill					;
;									;
; Returns:								;
;									;
; Error Returns:							;
;									;
; Registers Preserved:							;
;	All are preserved						;
;									;
; Registers Destroyed:							;
;									;
; Calls:								;
;									;
; History:								;
;									;
;  Mon March 9, 1987   -by-  Bob Gunderson (bobgu)			;
;Initail version							;
;-----------------------------------------------------------------------;

if KDEBUG

	assumes	ds, nothing
	assumes	es, nothing

cProc	lfillCC,<PUBLIC,NEAR>
cBegin nogen
	push	di
	push	cx
	push	ax

; if heap free checking is off, don't fill the block.

	mov	di,pLocalHeap
        cmp     [di].hi_check,2         ; 2 -> checkfree
        jb      fillexit

	lea	di,[bx].la_freefixedsize
	mov	cx,[bx].la_next
	cmp	cx,bx			    ; marker block ?
	jz	fillexit
	sub	cx,di
        mov     al,DBGFILL_FREE
	push	ds
	pop	es
	cld
	rep	stosb

fillexit:
	pop	ax
	pop	cx
	pop	di
	ret
cEnd nogen

endif


;-----------------------------------------------------------------------;
; lcheckCC								;
;									;
; checks all bytes in the specified block for the value DBGFILL_FREE    ;
;									;
; Arguments:								;
;	BX = Address of block to check					;
;									;
; Returns:								;
;	ZF = 0 if block does not contain all 0CCh values, else ZF = 1	;
;									;
; Error Returns:							;
;									;
; Registers Preserved:							;
;	All are preserved						;
;									;
; Registers Destroyed:							;
;									;
; Calls:								;
;									;
; History:								;
;									;
;  Mon March 9, 1987   -by-  Bob Gunderson (bobgu)			;
;Initail version							;
;-----------------------------------------------------------------------;

if KDEBUG

	assumes	ds, nothing
	assumes	es, nothing

cProc	lcheckCC,<PUBLIC,NEAR>
cBegin nogen
	push	si
	push	cx
	push	ax

; if heap checking is off, don't check the block

	mov	si,pLocalHeap
        xor     cx,cx           ; cx == 0 for ok return
        cmp     [si].hi_check,2
        jb      testexit

	lea	si,[bx].la_freefixedsize
	mov	cx,[bx].la_next
        cmp     cx,bx           ; sentinel block ?
        jz      testexit2       ; yes: return ZF = 1 for success
	sub	cx,si
	push	ds
	pop	es
	cld
testloop:
	lodsb
        cmp     al,DBGFILL_FREE
	loope	testloop

testexit:
	or	cx,cx		; ZF = 1 if ok, ZF = 0 if failed test
testexit2:
	pop	ax
	pop	cx
	pop	si
	ret
cEnd nogen

endif

sEnd	CODE

end