	TITLE	LINTERF - Local memory allocator, interface procedures

.xlist
include tdb.inc
include kernel.inc
.list

.errnz	 la_prev		; This code assumes la_prev = 0

if KDEBUG
CheckHeap  MACRO n
local	a
	extrn	CheckLocalHeap:NEAR
	cCall	CheckLocalHeap
	or	ax,ax
	jz	a
	or	ax,ERR_LMEM
	kerror	<>,<&n: Invalid local heap>
a:
	endm
else
CheckHeap  MACRO n
	endm
endif

externW pLocalHeap

externFP <GlobalHandle,GlobalReAlloc,GlobalSize,GlobalCompact,GlobalFlags>
externFP <GlobalLock,GlobalUnlock>
externFP <GlobalMasterHandle,GetCurrentTask>
externFP FarValidatePointer
externFP DibRealloc

DataBegin

externW curTDB
;externW MyCSDS

if KDEBUG
externW DebugOptions
endif

DataEnd

sBegin	CODE
assumes CS,CODE
assumes DS,NOTHING
assumes ES,NOTHING

externNP <halloc,lhfree,lhdref,hthread>		; LHANDLE.ASM
externNP <ljoin,lrepsetup,lzero>		; LALLOC.ASM
externNP <lalloc,lfree,lfreeadd,lfreedelete>	; LALLOC.ASM
externNP <lcompact,lshrink>			; LCOMPACT.ASM

if KDEBUG
externNP <CheckLAllocBreak>
endif   ;KDEBUG

;-----------------------------------------------------------------------;
; lenter								;
;									;
; Enters a critical region for the local heap.				;
;									;
; Arguments:								;
;	DS = automatic data segment containing local heap		;
;									;
; Returns:								;
;	DS:DI = address of LocalInfo for local heap			;
;	(li_lock field has been incremented)				;
;									;
; Error Returns:							;
;	CX == 1 if heap is busy						;
;									;
; Registers Preserved:							;
;	AX,BX,DX,SI,ES							;
;									;
; Registers Destroyed:							;
;	CX								;
;									;
; Calls:								;
;	nothing								;
;									;
; History:								;
;									;
;  Sun Oct 13, 2086 09:27:27p  -by-  David N. Weise   [davidw]		;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

cProc	lenter,<PUBLIC,NEAR>
cBegin nogen
	mov	di,pLocalHeap
	mov	cx,1
	xchg	[di].li_lock,cx
	jcxz	enter1

; Should really do a WaitEvent

if KDEBUG
	xor	bx,bx
	kerror	ERR_LMEMCRIT,<lenter: local heap is busy>,bx,cx
endif

enter1: ret

cEnd nogen

;-----------------------------------------------------------------------;
; lleave								;
;									;
; Leaves a critical region for the local heap.				;
;									;
; Arguments:								;
;	DS = automatic data segment containing local heap		;
;									;
; Returns:								;
;	DS:DI = address of LocalInfo for local heap			;
;	(li_lock field has been cleared)				;
;									;
; Error Returns:							;
;									;
; Registers Preserved:							;
;	AX,BX,DX,SI,ES							;
;									;
; Registers Destroyed:							;
;	CX								;
;									;
; Calls:								;
;	nothing								;
;									;
; History:								;
;									;
;  Sun Oct 13, 2086 09:30:01p  -by-  David N. Weise   [davidw]		;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

cProc	lleave,<PUBLIC,NEAR>
cBegin nogen
	mov	di,pLocalHeap
	xor	cx,cx
	xchg	ds:[di].li_lock,cx
	jcxz	leave1
	ret
leave1:

; Should really do a PostEvent
if KDEBUG
	kerror	ERR_LMEMCRIT,<lleave: local heap was not busy>
endif

cEnd nogen

;-----------------------------------------------------------------------;
; lhexpand								;
; 									;
; Expands a local handle table.						;
; 									;
; Arguments:								;
;	CX    = #handle entries to allocate				;
;	DS:DI = local info structure					;
; 									;
; Returns:								;
;	AX = address of handle table block of requested size		;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
;	BX,CX,DX,ES							;
; 									;
; Calls:								;
;	lalloc								;
;	hthread 							;
; 									;
; History:								;
; 									;
;  Tue Oct 14, 1986 01:48:21p  -by-  David N. Weise   [davidw]		;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

cProc	lhexpand,<PUBLIC,NEAR>
cBegin nogen
	xor	ax,ax			; Allocate fixed local block
	mov	bx,cx
	inc	bx			; plus 1 for word at beginning & end
	shl	bx,1
	shl	bx,1
	errnz	<4-SIZE LocalHandleEntry>
	push	cx
	call	lalloc
	pop	cx
        or      ax,ax
	jz	lhfail
	mov	bx,ax
	xchg	[di].hi_htable,bx
	push	di			; Save DI
	mov	di,ax
	mov	ds:[di],cx
	inc	di
	inc	di
	call	hthread
	mov	ds:[di],bx		; Store pointer to next block
	pop	di			; Restore DI
lhfail:	mov	cx,ax
	ret
cEnd nogen


;-----------------------------------------------------------------------;
; lalign								;
; 									;
; Aligns the size request for a local item to a multiple of 4 bytes.	;
; 									;
; Arguments:								;
;	CF = 0								;
;	  BX = #bytes							;
;	CF = 1	get max amount						;
; 									;
; Returns:								;
;	DX = #bytes aligned to next higher multiple of 4		;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
; 									;
; Calls:								;
;	nothing								;
; 									;
; History:								;
; 									;
;  Tue March 10, 1987 -by- Bob Gunderson [bobgu]			;
; To accomidate free list, must impose a minimum block size to prevent	;
; allocating a block on top of the extended header of previous block.	;
;									;
;  Tue Oct 14, 1986 01:56:42p  -by-  David N. Weise   [davidw]		;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

cProc	lalign,<PUBLIC,NEAR>
cBegin nogen
	jnc	align0
	mov	bx,LA_MASK
align0:	cmp	bx,SIZE LocalArenaFree
	jae	align2
	mov	bx,SIZE LocalArenaFree	; Must impose a minimum size
align2:	lea	dx,[bx+LA_ALIGN]
	and	dl,LA_MASK
	cmp	dx,bx			; Test for overflow
	jae	align1			; No, continue
	mov	dx,LA_MASK		; Yes, return largest possible size
align1:	ret
cEnd nogen


;-----------------------------------------------------------------------;
; ldref									;
; 									;
; Dereferences a local handle.						;
; 									;
; Arguments:								;
;	SI = handle							;
; 									;
; Returns:								;
;	AX = address of client data					;
;	BX = address of arena header					;
;	CH = lock count or zero for fixed objects			;
;	SI = handle table entry address or zero for fixed objects	;
;	ZF = 1 if NULL handle passed in					;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
;	CL 								;
;									;
; Calls:								;
;	lhdref								;
; 									;
; History:								;
; 									;
;  Tue Oct 14, 1986 01:58:58p  -by-  David N. Weise   [davidw]		;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

cProc	ldref,<PUBLIC,NEAR>
cBegin nogen
	xor	cx,cx			; Zero lock count
	mov	ax,si			; Return handle if fixed object
	test	al,LA_MOVEABLE		; Is it moveable?
	jnz	dref3			; Yes, go dereference handle
	xor	si,si			; Set SI to zero for fixed objects
	or	ax,ax			; No, were we passed NULL?
	jz	dref2			; Yes, then all done
dref1:	mov	bx,ax			; No, return BX pointing
	and	bl,LA_MASK		; ...to arena header
	sub	bx,la_fixedsize		; Leaving ZF = 0
dref2:	ret

dref3:	call	lhdref			; Get true address in AX
					;  and lock count in CH
ife KDEBUG
	jz	done			; Compute arena header if valid true address
	mov	bx,ax			; See if arena header points to
	sub	bx,SIZE LocalArena
done:	ret				; No, return with ZF = 1
else
	test	cl,LHE_DISCARDED	; Discarded?
	jnz	dref5			; Yes, all done
	or	ax,ax			; Is there a true address?
	jz	dref4			; No, then must be error
	mov	bx,ax			; See if arena header points to
	sub	bx,SIZE LocalArena
	cmp	[bx].la_handle,si	; handle table entry?
	je	dref5			; Yes, continue
dref4:	xor	bx,bx
	kerror	ERR_LMEMHANDLE,<LDREF: Invalid local handle>,bx,si
	xor	ax,ax
dref5:	or	ax,ax
	ret
endif

cEnd nogen


;-----------------------------------------------------------------------;
; lnotify								;
; 									;
; Calls the local heap's notify proc (if any).				;
; 									;
; Arguments:								;
;	AL = message code						;
;	BX = handle or largest free block				;
;	CX = optional argument 		 				;
; 									;
; Returns:								;
;	AX = return value from notify proc or AL			;
;	ZF = 1 if AX = 0						;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
; 									;
; Registers Destroyed:							;
;	BX,CX,DX,ES							;
; 									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Tue Oct 14, 1986 02:03:14p  -by-  David N. Weise   [davidw]		;
; Added this nifty comment block.					;
;-----------------------------------------------------------------------;

cProc	lnotify,<PUBLIC,NEAR>
cBegin nogen
	cmp	word ptr [di+2].li_notify,0
	je	notify1
	xor	ah,ah
	cCall	[di].li_notify,<ax,bx,cx>
notify1:
	or	ax,ax
	ret
cEnd nogen


; The remainder of this file implements the exported interface to the
; local memory manager.  A summary follows:
;
;   HANDLE  far PASCAL LocalAlloc( WORD, WORD );
;   HANDLE  far PASCAL LocalReAlloc( HANDLE, WORD, WORD );
;   HANDLE  far PASCAL LocalFree( HANDLE );
;   WORD    far PASCAL LocalSize( HANDLE );
;   char *  far PASCAL LocalLock( HANDLE );
;   BOOL    far PASCAL LocalUnlock( HANDLE );
;   WORD    far PASCAL LocalCompact( WORD );
;   WORD    far PASCAL LocalShrink( HANDLE , WORD );
;   FARPROC far PASCAL LocalNotify( FARPROC );
;   #define LocalDiscard( h ) LocalReAlloc( h, 0, LMEM_MOVEABLE )
;   BOOL    far PASCAL LocalInit( char *, char * );
;
;   extern WORD * PASCAL pLocalHeap;
;
;   #define dummy 0
;   #define LocalFreeze( dummy ) ( *(pLocalHeap+1) += 1 )
;   #define LocalThaw( dummy )	 ( *(pLocalHeap+1) -= 1 )
;

cProc	ILocalAlloc,<PUBLIC,FAR>,<si,di>
	parmW   flags
	parmW   nbytes
cBegin
	WOWTrace "LocalAlloc(#AX,#BX)",<<ax,flags>,<bx,nbytes>>
	CheckHeap LocalAlloc
	call	lenter			; About to modify memory arena
	jcxz	la_ok
	xor	ax, ax
	jmp	la_crit
la_ok:
	mov	ax,flags		; Allocate space for object
	test	al,LA_NOCOMPACT
	jz	all0
	inc	[di].hi_freeze
all0:	mov	bx,nbytes
	or	bx,bx			; Zero length?
	jnz	alloc0			; No, continue
	and	ax,LA_MOVEABLE		; Yes, moveable?
	jz	alloc1			; No, return error
	call	halloc			; Yes, allocate handle
	or	ax,ax			; failure??
	jz	alloc1			; yep... return a NULL
	xor	byte ptr [bx].lhe_address,LA_MOVEABLE ; and zero address field
	or	byte ptr [bx].lhe_flags,LHE_DISCARDED ; and mark as discarded
	jmps	alloc1			; all done

alloc0:	test	al,LA_MOVEABLE		; Is this a moveable object?
	jz	dont_need_handle
	push	ax
	push	bx
	call	halloc			; Allocate handle first.

	or	ax,ax
	jnz	all2			; error?
	pop	bx			; yes, DON'T destroy the NULL in AX
	pop	bx
	jmps	alloc1
all2:
	pop	bx
	pop	ax

;	pop	bx
;	pop	ax
;	jcxz	alloc1

	push	cx			; this is handle
	call	lalloc
	pop	si			; get handle in index register
	jnz	got_space
	call	lhfree			; free the allocated handle
	jmps	alloc1

got_space:
	mov	[si].lhe_address,ax	; Store address away.
	mov	bx,ax			; Store back link to handle in header
	mov	[bx-SIZE LocalArena].la_handle,si  ; Mark as moveable block
	or	byte ptr [bx-SIZE LocalArena].la_prev,LA_MOVEABLE
	mov	ax,si			; return the handle
	and	dh,LHE_DISCARDABLE	; Discardable object?
	jz	alloc1			; No, continue
	mov	[si].lhe_flags,dh	; Yes, save discard level in handle
	jmps	alloc1			;  table entry

dont_need_handle:
	call	lalloc
alloc1:	test	byte ptr flags,LA_NOCOMPACT
	jz	all1
	dec	[di].hi_freeze
all1:	call	lleave			; Arena is consistent now
la_crit:
	or	ax,ax
	jnz	@F
        KernelLogError DBF_WARNING,ERR_LALLOC,"LocalAlloc failed"  ; LocalAlloc failure
	xor	ax, ax			; preserve the return value
@@:
	mov	cx,ax			; Let caller do jcxz to test failure
	WOWTrace "LocalAlloc: #AX"
cEnd


cProc	ILocalReAlloc,<PUBLIC,FAR>,<si,di>
	parmW   h
	parmW   nbytes
	parmW   rflags
cBegin
	WOWTrace "LocalReAlloc(#AX,#BX,#CX)",<<ax,h>,<bx,nbytes>,<cx,rflags>>
	CheckHeap   LocalReAlloc
	call	lenter			; About to modify memory arena
	jcxz	lr_ok
	xor	ax, ax
	jmp	lr_crit
lr_ok:
	test	byte ptr rflags,LA_NOCOMPACT
	jz	rel0
	inc	[di].hi_freeze
rel0:
	mov	si,h			; Dereference handle
	call	ldref
	jz	racreate		; If zero handle, check for re-creation.
	test	byte ptr rflags,LA_MODIFY   ; Want to modify handle table flags
	jnz	ramodify		; Yes, go do it
	mov	si,bx			; SI = arena header
	mov	bx,ax			; Compute address of new next header
	mov	dx,nbytes		;  assuming there is room.
	cmp	dx,SIZE LocalArenaFree	; Minimum size must be large enough
	jae	@F			;  to a hold free header.
	mov	dx,SIZE LocalArenaFree
@@:	add	bx,dx
	call	lalign
	mov	bx,[si].la_next		; Get address of current next header
	cmp	nbytes,0		; Are we reallocing to zero length?
	jne	raokay			; No, continue
	jcxz	radiscard		; Yes, discard if not locked
rafail:
        KernelLogError  DBF_WARNING,ERR_LREALLOC,"LocalReAlloc failed"
	xor	ax,ax
	jmp	raexit

radiscard:

; Here to discard object, when reallocating to zero size.  This
;  feature is only enabled if the caller passes the moveable flag

	test	byte ptr rflags,LA_MOVEABLE	; Did they want to discard?
	jz	rafail			; No, then return failure.
	mov	al,LN_DISCARD		; AL = discard message code
	xor	cx,cx			; CX = disc level of 0 (means realloc)
	mov	bx,h			; BX = handle
	call	lnotify			; See if okay to discard
	jz	rafail			; No, do nothing
	xor	ax,ax			; Yes, free client data
	mov	bx,si
	call	lfree
	jz	rax			; Return NULL if freed a fixed block
	mov	[si].lhe_address,ax	; No, zero addr in handle table entry
	or	[si].lhe_flags,LHE_DISCARDED  ; and mark as discarded
	jmps	rasame			; Return original handle, except
					; LocalLock will now return null.

ramodify:
	mov	ax,rflags		; Get new flags
	or	si,si			; Moveable object?
	jz	rasame			; No, all done
	and	[si].lhe_flags,not LHE_USERFLAGS ; Clear old flags
	and	ah,LHE_USERFLAGS; Get new flags
	or	[si].lhe_flags,ah	; Store new flags in handle table entry
	jmps	rasame

racreate:
	test	cl,LHE_DISCARDED	; Is this a discarded handle?
	jz	rasame			; No, return original value
	mov	bx,nbytes		; BX = new requested size
	push	si			; save handle
	mov	ax,LA_MOVEABLE		; Reallocating a moveable object
	or	ax,rflags		; ...plus any flags from the caller
	call	lalloc			; Allocate a new block
	pop	si			; restore existing handle
	jz	rafail
	xor	[si].lhe_flags,LHE_DISCARDED  ; and mark as not discarded
	jmp	ram2

raokay:
; Here if not trying to realloc this block to zero
; SI = arena header of current block
; AX = client address of current block
; BX = arena header of next block
; CH = lock count of current block
; DX = new next block, based on new requested size
	cmp	dx,bx			; Are we growing or shrinking?
	ja	ragrow			; We are growing
rashrink:				; We are shrinking
; Here to shrink a block
; SI = arena header of current block
; BX = arena header of next block
; DX = new next block, based on new requested size
	push	si
	mov	si,dx			; SI = new next block
	add	dx,LA_MINBLOCKSIZE	; Test for small shrinkage
        jnc     @F                      ;   No overflow
        pop     bx			;   Overflowed, obviously no room!
	jmp     rasame                  ;   Clear stack and return same handle
@@:	cmp	dx,bx		  	; Is there room from for free block?
	pop	bx			; BX = current block
	jae	rasame			; No, then no change to make
					; Splice extra free space into arena
	mov	cx,si			; [si].la_next = [bx].la_next
	xchg	[bx].la_next,cx		; [bx].la_next = si
	mov	[si].la_prev,bx		; [si].la_prev = bx
	xchg	si,cx
	and	[si].la_prev,LA_ALIGN
	jz	splice1			; If free then coelesce
	inc	[di].hi_count		; No, adding new arena entry
	jmps	splice2
splice1:

; next block is free, must make the new block a larger free block

; first remove the current free block [SI] from the free list

	xchg	bx,si
	call	lfreedelete
	xchg	bx,si
	mov	si,[si].la_next
	and	[si].la_prev,LA_ALIGN

splice2:
	or	[si].la_prev,cx		; [[si].la_next].la_prev = si
	xchg	si,cx
	mov	[si].la_next,cx
	mov	bx,si			; BX = new block
	xor	si,si			; don't know where to insert
	call	lfreeadd		; add to free list
rasame:
	mov	ax,h			; Return the same handle
rax:	jmps	raexit			; All done

; Here to try to grow the current block
; AX = client address of current block
; SI = arena header of current block
; BX = arena header of next block
; DX = new next block, based on new requested size
ragrow:
if KDEBUG
        call    CheckLAllocBreak
        jc      rafail1
endif

	test	byte ptr [bx].la_prev,LA_BUSY	; Is next block free?
	jnz	ramove			; No, try to move the current block
	cmp	dx,[bx].la_next		; Yes, is free block big enough?
	ja	ramove			; No, try to move the current block
	mov	cx,bx			; Yes, save free block address in CX
	call	ljoin			; and attach to end of current block
	test	rflags,LA_ZEROINIT	; Zero fill extension?
	jz	ranz			; No, continue
	call	lzero			; Yes, zero fill
ranz:
	jmp	rashrink		; Now shrink block to correct size

; Here to try to move the current block
; AX = client address of current block
; SI = arena header of current block
; CH = lock count of current block
ramove:
	mov	dx,rflags		; get the passed in flags
	mov	bx,LA_MOVEABLE		; Determine if okay to move this guy
	jcxz	ramove1			; Continue if this handle not locked
	test	dx,bx			; Locked.  Did they say move anyway?
	jnz	ramove1			; Yes, go do it
rafail1:jmp	rafail
ramove1:
	or	dx,bx			; make sure moveable bit set
	test	h,bx			; Is it a moveable handle?
	jnz	ram1			; Yes, okay to move
	test	rflags,bx		; No, did they say it's okay to move?
	jz	rafail1			; No, then fail
	xor	dx,bx			; turn off moveable bit
ram1:
;   We do this because the lalloc can move the old block to a
;   block that is larger than the requested new block.	This can
;   happen if we LocalCompact durring the lalloc call.	(bobgu 8/27/87)
	mov	bx,[si].la_next
	sub	bx,ax			; # client bytes in current block
	push	bx			; save it for later

	mov	ax,dx			; AX = allocation flags
	mov	bx,nbytes		; BX = new requested size
	call	lalloc			; Allocate a new block

	pop	cx			; CX = client size of old block
	jz	rafail1
	push	cx			; save it away again

	push	ax			; Call notify proc
	mov	cx,ax			; with new location
	mov	bx,h			; handle
	mov	al,LN_MOVE
	call	lnotify			; Notify client of new location
	mov	si,h
	call	ldref			; BX = old arena header address
	mov	si,ax			; SI = old client address
	pop	ax			; AX = new client address

	pop	cx			; get back size of old client
;	mov	cx,[bx].la_next 	; Compute length of old client data
;	sub	cx,si
	call	lrepsetup		; Setup for copy of words
	push	di
	mov	di,ax			; DI = new client data address
	rep	movsw			; Copy old client data to new area
	pop	di			; Restore DI
	call	lfree			; Free old block
	jz	raexit
ram2:
	mov	[si].lhe_address,ax	; Set new client data address
	xchg	ax,si			; Return original handle
					; Set back link to handle in new block
	or	byte ptr [si-SIZE LocalArena].la_prev,LA_MOVEABLE
	mov	[si-SIZE LocalArena].la_handle,ax
raexit:
	test	byte ptr rflags,LA_NOCOMPACT
	jz	rel1
	dec	[di].hi_freeze
rel1:
	call	lleave			; Arena is consistent now
lr_crit:
	mov	cx,ax			; Let caller do jcxz to test failure
	WOWTrace "LocalReAlloc: #AX"
cEnd


cProc	ILocalFree,<PUBLIC,FAR>,<si,di>
	parmW   h
cBegin
	WOWTrace "LocalFree(#AX)",<<ax,h>>
	call	lenter			; About to modify memory arena
	jcxz	lf_ok
	xor	ax, ax
	jmp	lf_crit
lf_ok:
	CheckHeap   LocalFree
	mov	si,h			; Dereference handle
	call	ldref
	jz	free1			; Free handle if object discarded
if KDEBUG
	push	ds
	SetKernelDS
	mov	ds,curTDB
	assumes	ds, nothing
	cmp	ds:[TDB_ExpWinVer],201h
	pop	ds
	jb	dont_do_error_checking

	or	ch,ch			; No, is the handle locked?
	jz	freeo			; Yes, then don't free
	xor	bx,bx
	kerror	ERR_LMEMUNLOCK,<LocalFree: freeing locked object>,bx,h
	mov	si,h			; Dereference handle again
	call	ldref
freeo:
dont_do_error_checking:
endif
	call	lfree			; No, free the object
free1:	call	lhfree			; and any handle
freex:	call	lleave			; Arena is consistent now
lf_crit:
	WOWTrace "LocalFree: #AX"
cEnd

cProc	ILocalSize,<PUBLIC,FAR>
;	parmW   h
cBegin	nogen
;	CheckHeap   LocalSize
	push	si
	mov	si,sp
	mov	si,ss:[si+6]		; Dereference handle
	call	ldref			; into BX
	jz	size1			; Done if AX = zero
	sub	ax,[bx].la_next		; Otherwise size =
	neg	ax			;  - (client address - next block address)
size1:	mov	cx,ax			; Let caller do jcxz to test failure
	pop	si
	ret	2
cEnd	nogen


cProc	ILocalFlags,<PUBLIC,FAR>
;	parmW   h
cBegin	nogen
;	CheckHeap   LocalFlags
	push	si
	mov	si,sp
	mov	si,ss:[si+6]		; Dereference handle
	call	ldref			; into BX
	mov	cx,si
	jcxz	flags1			; Done if not moveable
	mov	cx,word ptr [si].lhe_flags   ; Return flags and lock count
flags1:
	xchg	cl,ch			; Return lock count in low half
	mov	ax,cx			; Let caller do jcxz to test failure
	pop	si
	ret	2
cEnd	nogen



if KDEBUG
cProc	ILocalLock,<PUBLIC,FAR>,<si>
	parmW   h
cBegin
	WOWTrace "LocalLock(#AX)",<<ax,h>>
;	CheckHeap   LocalLock
	mov	si,h
	call	ldref			; SI = handle table entry
	jz	lock2			; Done if invalid handle or discarded
	or	si,si
	jz	lock2			; or if not moveable

	inc	[si].lhe_count		; Increment usage count
	jnz	lock1
	xor	cx,cx
	kerror	ERR_LMEMLOCK,<LocalLock: Object usage count overflow>,cx,h
	dec	[si].lhe_count		; Keep pinned at max value
lock1:	mov	ax,[si].lhe_address	; Return true address in AX
lock2:	
	or	ax, ax
	jnz	@F
        KernelLogError  DBF_WARNING,ERR_LLOCK,"LocalLock failed"
        xor     ax,ax                   ; Get back the NULL value in ax
@@:	mov	cx,ax			; Let caller do jcxz to test failure
	WOWTrace "LocalLock: #AX"
cEnd

else
cProc	ILocalLock,<PUBLIC,FAR>
;	parmW   h
cBegin	nogen
	mov	bx,sp			; Get handle parameter from stack
	mov	bx,SS:[bx+4]
	mov	ax,bx			; Return in AX
	test	bl,LA_MOVEABLE		; Test for moveable (also null)
	jz	lock2			; Return if not moveable or null
	test	[bx].lhe_flags,LHE_DISCARDED  ; Return zero if discarded
	jnz	lock1
	inc	[bx].lhe_count		; Increment usage count
	jz	lock3			; Special case if overflow
lock1:
	mov	ax,[bx].lhe_address	; Return true address in AX or zero
lock2:	mov	cx,ax			; Let caller do jcxz to test failure
	ret	2
lock3:
	dec	[bx].lhe_count		; Ooops, keep pinned at max value
	jmp	lock1

cEnd nogen
endif


if KDEBUG
cProc	ILocalUnlock,<PUBLIC,FAR>,<si>
	parmW   h
cBegin
	WOWTrace "LocalUnlock(#AX)",<<ax,h>>
;	CheckHeap   LocalUnlock
	mov	si,h
	call	ldref			; SI = handle table entry
	jz	unlock1			; Done if invalid handle or discarded
	xor	ax,ax
	or	si,si
	jz	unlock1			; or if not moveable

	or	ch, ch
	jz	unlockerr
	dec	ch			; Decrement usage count
	cmp	ch,0FFh-1		; 0 -> ff, ff -> fe
	jae	unlock1			; Return if pinned or already unlocked
	mov	[si].lhe_count,ch
;	mov	ax,bx
	mov	al,ch
	cbw
	jmps	unlock1
unlockerr:
	xor	cx,cx
	kerror	ERR_LMEMUNLOCK,<LocalUnlock: Object usage count underflow>,cx,h
	xor	ax,ax
unlock1:
	mov	cx,ax			; Let caller do jcxz to test failure
	WOWTrace "LocalUnlock: #AX"
cEnd

else
cProc	ILocalUnlock,<PUBLIC,FAR>
;	parmW   h
cBegin	nogen
	mov	bx,sp			; Get handle parameter from stack
	mov	bx,SS:[bx+4]
	xor	ax,ax
	test	bl,LA_MOVEABLE		; Test for moveable (also null)
	jz	unlock1			; Return if not moveable or null
	mov	cx,word ptr [bx].lhe_flags
	errnz	<2-lhe_flags>
	errnz	<3-lhe_count>
	and	cl,LHE_DISCARDED
	jnz	unlock1			; Return if discarded
	dec	ch			; Decrement usage count
	cmp	ch,0FFh-1		; 0 -> ff, ff -> fe
	jae	unlock1			; Return if pinned or already unlocked
	mov	[bx].lhe_count,ch
;	mov	ax,bx
	mov	al,ch
	cbw
unlock1:
	mov	cx,ax			; Let caller do jcxz to test failure
	ret	2
cEnd nogen
endif



cProc	LocalHandle,<PUBLIC,FAR>
;	parmW   h
cBegin	nogen
	mov	bx,sp
	mov	bx,SS:[bx+4]
	test	bl,LA_MOVEABLE
	jz	lh1
	mov	ax,bx
	mov	bx,[bx-SIZE LocalArena].la_handle
	cmp	[bx].lhe_address,ax
	je	lh1
	xor	bx,bx
lh1:
	mov	ax,bx
	ret	2
cEnd	nogen


cProc	LocalCompact,<PUBLIC,FAR>,<si,di>
	parmW   minBytes
cBegin
	CheckHeap   LocalCompact
	call	lenter			; About to modify memory arena
	jcxz	lc_ok
	xor	ax, ax
	jmp	lc_crit
lc_ok:
	mov	bx,minBytes
	clc
	call	lalign
	call	lcompact
	or	ax,ax
	jz	compact1
	sub	ax,SIZE LocalArena	; Reduce available size by header size
compact1:
	call	lleave			; Arena is consistent now
lc_crit:
cEnd


cProc	LocalShrink,<PUBLIC,FAR>,<si,di>
	parmW  hseg
	parmW  wsize
cBegin
	mov	ax,hseg
	or	ax,ax			; use heap in current DS ?
	jz	ls_useds		; yes

; Use the segment handle passed
	push	ax
	call	GlobalHandle
	or	ax,ax			; valid handle ?
	jz	ls_errexit		; no....
	mov	ds,dx			; set the proper DS
ls_useds:
; check the heap and lock it
	CheckHeap   LocalShrink
	call	lenter			; About to modify memory arena
	jcxz	ls_ok
	xor	ax, ax
	jmp	short ls_crit
ls_ok:

	mov	bx,wsize		; get requested min size
	call	lshrink 		; Let's get small
; AX = new local heap size
	call	lleave			; Arena is consistent now
ls_crit:
ls_errexit:

cEnd


cProc	LocalNotifyDefault,<PUBLIC,FAR>,<si,di>
	parmW   msg
	parmW	handle		; or largest free block
	parmW   arg1
cBegin
	mov	ax,msg
	or	ax,ax
	jnz	dlnexit1
	cCall	GlobalHandle,<ds>
	or	ax,ax
	jz	dlnexit1
			; Fix for FORMBASE who uses a fixed
			; segment for a local heap.  This blows
			; up if we cause the calling segment
			; to be discarded since the DS saved by
			; Local???? is a fixed segment which
			; SearchStack can't handle.  Confused?
			; This was not a problem in 2.x because
			; 2.x failed to grow a fixed object.
			; Using a fixed segment for a local heap
			; is not valid and this is really a problem
			; with FORMBASE.
	mov	si,ax
	cCall	GlobalFlags,<si>	; Get flags
	xchg	ah, al
	push	ax
	cCall	GlobalSize,<si>
	sub	ax, handle		; Temorarily subtract out largest free
	pop	bx			; Get flags in BX
	xor	cx,cx
	add	ax,arg1
	adc	dx,cx			; Fail if DS attempts to grow
	jnz	dln_too_large		;  beyond 64k.
	add	ax,18h			; since li_extra isn't guaranteed
	adc	dx,cx
	jnz	dln_too_large
	add	ax,[di].li_extra
	adc	dx,cx			; Fail if DS attempts to grow
	jnz	@F			;  beyond 64k.
	add	ax, handle		; add back largest free
	adc	dx, cx					       
	jnz	@F
	cmp	ax,0FFF0h
	jbe	dln0
@@:	mov	ax,0FFF0h
	xor	dx,dx
	jmps	dln0
dln_too_large:
	xor	ax,ax
dlnexit1:
	jmp	dlnexit
dln0:
	test	si,GA_FIXED		; Is DS fixed?
	jnz	dln1			; Yes, must grow in place
	cmp	bh,1			; No, is lock count 1?
	jne	dln1			; No must grow in place if locked
	or	cl,GA_MOVEABLE		; Yes, okay to move even though locked
dln1:
	push	bx
grow_DS:
	cCall	GlobalReAlloc,<si,dxax,cx>
	pop	bx
	jcxz	dlnexit
	push	bx
	cCall	GlobalSize,<ax>

	or	dx,dx			; Did we get rounded up >= 64K?
	jz	@F			; No, OK
	mov	ax,0FFFFh		 ; This only happens under 386pmode
@@:
	mov	bx,ax
	sub	bx,la_freefixedsize
	and	bl,LA_MASK
	mov	di,ds:[pLocalHeap]
	mov	si,[di].hi_last
	mov	[bx].la_next,bx
	mov	[bx].la_prev,si
	or	byte ptr [bx].la_prev,LA_BUSY
	mov	[si].la_next,bx

; Maintain the free list.

	mov	ax,[si].la_free_prev
	mov	[bx].la_free_prev,ax
	mov	[bx].la_free_next,bx
	mov	[bx].la_size,WORD PTR la_freefixedsize
	push	si
	mov	si,ax
	mov	[si].la_free_next,bx
	pop	si

	mov	[di].hi_last,bx
	inc	[di].hi_count
	mov	bx,si
	call	lfree

;   Don't do this... (bobgu 8/4/87)
;	stc
;	call	lalign
;	call	lcompact

	mov	ax,1
	pop	bx
	mov	ax,1
dlnexit:
cEnd

cProc   LocalNotifyDib,<PUBLIC,FAR>,<si,di>
	parmW   msg
	parmW	handle		; or largest free block
	parmW   arg1
cBegin
	mov	ax,msg
	or	ax,ax
        jnz     dlnexit1dib
	cCall	GlobalHandle,<ds>
	or	ax,ax
        jz      dlnexit1dib
			; Fix for FORMBASE who uses a fixed
			; segment for a local heap.  This blows
			; up if we cause the calling segment
			; to be discarded since the DS saved by
			; Local???? is a fixed segment which
			; SearchStack can't handle.  Confused?
			; This was not a problem in 2.x because
			; 2.x failed to grow a fixed object.
			; Using a fixed segment for a local heap
			; is not valid and this is really a problem
			; with FORMBASE.
	mov	si,ax
	cCall	GlobalFlags,<si>	; Get flags
	xchg	ah, al
	push	ax
	cCall	GlobalSize,<si>
	sub	ax, handle		; Temorarily subtract out largest free
	pop	bx			; Get flags in BX
	xor	cx,cx
	add	ax,arg1
	adc	dx,cx			; Fail if DS attempts to grow
        jnz     dib_too_large           ;  beyond 64k.
	add	ax,18h			; since li_extra isn't guaranteed
	adc	dx,cx
        jnz     dib_too_large
	add	ax,[di].li_extra
	adc	dx,cx			; Fail if DS attempts to grow
	jnz	@F			;  beyond 64k.
	add	ax, handle		; add back largest free
	adc	dx, cx					       
	jnz	@F
	cmp	ax,0FFF0h
        jbe     dln0dib
@@:	mov	ax,0FFF0h
	xor	dx,dx
        jmps    dln0dib
dib_too_large:
	xor	ax,ax
dlnexit1Dib:
        jmp     dlnexitdib
dln0dib:
	test	si,GA_FIXED		; Is DS fixed?
        jnz     dln1dib                 ; Yes, must grow in place
	cmp	bh,1			; No, is lock count 1?
        jne     dln1dib                 ; No must grow in place if locked
	or	cl,GA_MOVEABLE		; Yes, okay to move even though locked
dln1dib:
	push	bx
        cCall   DibReAlloc,<ds,ax>
        or      ax,ax
        jz      dlnexitdib0
        cCall   GlobalSize,<si>

	or	dx,dx			; Did we get rounded up >= 64K?
	jz	@F			; No, OK
	mov	ax,0FFFFh		 ; This only happens under 386pmode
@@:
	mov	bx,ax
	sub	bx,la_freefixedsize
	and	bl,LA_MASK
	mov	di,ds:[pLocalHeap]
	mov	si,[di].hi_last
	mov	[bx].la_next,bx
	mov	[bx].la_prev,si
	or	byte ptr [bx].la_prev,LA_BUSY
	mov	[si].la_next,bx

; Maintain the free list.

	mov	ax,[si].la_free_prev
	mov	[bx].la_free_prev,ax
	mov	[bx].la_free_next,bx
	mov	[bx].la_size,WORD PTR la_freefixedsize
	push	si
	mov	si,ax
	mov	[si].la_free_next,bx
	pop	si

	mov	[di].hi_last,bx
	inc	[di].hi_count
	mov	bx,si
	call	lfree

;   Don't do this... (bobgu 8/4/87)
;	stc
;	call	lalign
;	call	lcompact


        mov     ax,1
dlnexitdib0:
	pop	bx
dlnexitdib:
cEnd

sEnd	CODE

externFP Far_lalign
externFP Far_lrepsetup
if KDEBUG
externFP Far_lfillCC
endif

sBegin	NRESCODE
assumes CS,NRESCODE
assumes DS,NOTHING
assumes ES,NOTHING

cProc	ILocalNotify,<PUBLIC,FAR>
;	parmD   lpProc
cBegin	nogen
	mov	bx,sp
	mov	ax,SS:[bx+4]
	mov	dx,SS:[bx+6]
	mov	bx,ds:[pLocalHeap]
	xchg	word ptr [bx].li_notify,ax
	xchg	word ptr [bx].li_notify+2,dx
	ret	4
cEnd	nogen


cProc	LocalInit,<PUBLIC,FAR>,<ds,si,di>
	parmW	pseg
	parmW	pstart
	parmW	pend
cBegin

; Init current DS if none passed.

	mov	cx,pseg
	jcxz	li1
	mov	ds,cx
li1:

; Place local arena info at the beginning

	mov	bx,pstart
	or	bx,bx
	jnz	li2
	cCall	GlobalSize,<ds>

; Here we must do a little checking... The global memory manager may have
;  rounded up the size on us so that the DS is >=64K! If this has happened,
;  we can simply ignore the extra (since app can't look  at it anyway) and
;  pretend the DS is actually 0000FFFFH bytes big.

	or	dx,dx			; Did we get rounded up >= 64K?
	jz	li1a			; No, OK
	mov	ax,0FFFFH		; Pretend heap is 64K-1
li1a:
	mov	bx,ax
	dec	bx
	xchg	pend,bx
	sub	bx,pend
	neg	bx			; BX = first byte in arena
li2:
	clc
	call	Far_lalign
	mov	bx,dx			; DX = addr of first block to use

; OK, so here's how it works... In order to keep a free block list, there
; are 4 blocks allocated initially.  First is a dummy marker block that is
; free but marked busy.  Second is the local arena information block which
; is a standard busy block.  Third is the really big free block.  And lastly
; is another busy type free block.  All free blocks have an extended header
; in order to keep a free block list.

; Reserve room for the first free busy block.

	lea	bx,[bx].la_freefixedsize ; move over first free block
	push	dx			; preserve first free block address
	clc
	call	Far_lalign
	mov	bx,dx			; BX = arena info block address
	pop	dx			; DX = first block address
	push	dx			; * Save the address of the first
	push	bx			; * two block on the stack for later

; DI = client address of info block.

	lea	di,[bx].la_fixedsize
	xor	ax,ax			; Zero local arena info
	mov	cx,pend
	cmp	bx,cx			; start > end?

;;;;;;; jae	lix
	jb	li21
	pop	dx			; clean up the stack first
	pop	dx
	jmp	lix
li21:
	sub	cx,di
	call	Far_lrepsetup
	push	di
	rep	stosw
	pop	di
	lea	bx,[di].SIZE LocalInfo
if KDEBUG
	mov	[di].hi_pstats,bx
	add	bx,SIZE LocalStats

ifdef DISABLE

; Set the heap checking flag.

	push	es
	push	dx
	cCall	GlobalMasterHandle
	mov	es,dx
	mov	ax,es:[hi_check]
	pop	dx
	pop	es
else
        push    es
        push    _DATA
        pop     es
assumes es,DATA
;
; hi_check = 0;
; if (DebugOptions & DBO_CHECKHEAP)
; {
;    hi_check = 1
;    if (DebugOptions & DBO_CHECKFREE)
;       hi_check = 2;
; }
;
        xor     ax,ax
        test    es:DebugOptions,DBO_CHECKHEAP
        jz      @F
        inc     ax
        test    es:DebugOptions,DBO_CHECKFREE
        jz      @F
        inc     ax
@@:
assumes es,NOTHING
        pop     es
endif
	mov	[di].hi_check,ax
endif

; set the rest of the heap info

	mov	byte ptr [di].hi_hdelta,32
	mov	byte ptr [di].hi_count,4
	mov	[di].hi_first,dx
	mov	word ptr [di].li_notify,codeOFFSET LocalNotifyDefault
	mov	word ptr [di].li_notify+2,codeBASE
	mov	word ptr [di].hi_hexpand,codeOFFSET lhexpand
	mov	[di].li_extra,512
	mov	[di].li_sig,LOCALHEAP_SIG

; Move SI to first aligned block after info record

	clc
	call	Far_lalign
	mov	si,dx

; Move BX to last aligned block at end of local heap

	mov	bx,pend			; BX = end address
	sub	bx,la_freefixedsize	; Make room for an arena header
	and	bl,LA_MASK		; Align downwards to 4 byte boundary

	cmp	bx, si			; If heap is too small, the
	ja	@f			;   supposed free block could be
	xor	ax, ax			;   beyond the end block
	jmp	lix
@@:

; Setup reserved pointer in DS to point to LocalInfo

	mov	[di].hi_last,bx
	mov	ds:[pLocalHeap],di

; Finish linking entries in the local heap.
;
;	DX = address of the first arena block.	Free busy marker.
;	DI = address of the element which contains the local heap
;	 information struc.
;	SI = address of large free block that is the initial heap.
;	BX = address of a zero length arena element that is used to
;	     mark the end of the local heap.
;
;
; This last arena element is always busy, with a length of
; zero.  This allows us to always calculate the length of an
; arena entry by subtracting the address of the arena element
; from the hi_next field of the arena element (see lsize subr)

	pop	di			; Get the address of the first two
	pop	dx			; ... blocks off the stack

; Setup first block in arena, busy free block.

	xchg	bx,dx			;bx = first block (temporarily)
	lea	ax,[bx+LA_BUSY]		; ...as a busy block
	mov	[bx].la_prev,ax		; point to self
	mov	[bx].la_next,di		; point to next
	mov	[bx].la_free_prev,bx	; previous free block is self
	mov	[bx].la_free_next,si	; next free is large block
	mov	[bx].la_size,WORD PTR la_freefixedsize ; set the block size
	xchg	bx,dx			; back to normal

; Setup block that contains info structure.

	xchg	dx,bx
	lea	ax,[bx+LA_BUSY]		; ...as a busy block
	xchg	dx,bx
	mov	[di].la_prev,ax		; point to previous block
	mov	[di].la_next,si		; point to next block

; Setup large free block with extended free block header.

	mov	[si].la_prev,di		; Point middle block to first and
	mov	[si].la_next,bx		;  last blocks
	mov	[si].la_free_prev,dx	; previous free block
	mov	[si].la_free_next,bx	; next free block
	mov	ax,bx
	sub	ax,si
	mov	[si].la_size,ax		; length of free block

if KDEBUG
	xchg	si,bx			; BX = large free block
	call	Far_lfillCC		; Fill with 0CCh
	xchg	si,bx
endif

; Setup last free block with extended header.

	mov	[bx].la_next,bx		; Point last block to middle and itself
	lea	ax,[si+LA_BUSY]		; ...as a busy block
	mov	[bx].la_prev,ax
	mov	[bx].la_free_prev,si	; previous free block
	mov	[bx].la_free_next,bx	; next free block is self
	mov	[bx].la_size,WORD PTR la_freefixedsize ; set the block size

; Set the minimum size in arena header.

	mov	bx,ds:[pLocalHeap]
	mov	ax,[bx].hi_last
	add	ax,SIZE LocalArenaFree
	sub	ax,[bx].hi_first
	mov	[bx].li_minsize,ax

	cCall	GlobalLock,<ds>		; Make moveable DS initially locked.
					; (see LocalNotifyDefault)
	mov	al,1
lix:
	mov	cx,ax
cEnd


;-----------------------------------------------------------------------;
; LocalCountFree							;
; 									;
; Return the count of free bytes in the local heap.  This was motivated	;
; by the InitApp routines that want at least 4K available to continue	;
; the app running.							;
; 									;
; Arguments:								;
;	DS = heap segment						;
;									;
; Returns:								;
;	AX = free bytes in local heap					;
;									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
;	all								;
;									;
; Registers Destroyed:							;
; 									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Sat Aug 15, 1987 04:35:55p  -by-  Bob Gunderson [bobgu]		;
; Wrote it.								;
;-----------------------------------------------------------------------;


cProc	LocalCountFree,<PUBLIC,FAR>
cBegin nogen
	push	di
	push	si
	mov	di,pLocalHeap
	lea	si, [di].li_sig
	cCall	FarValidatePointer,<ds,si>
	or	ax, ax			; OK pointer?
	jz	countexit		;   no, just exit
	xor	ax,ax			; start with 0 bytes free
	cmp	[di].li_sig, LOCALHEAP_SIG	
	jne	countexit		;   No local heap!!
	mov	si,[di].hi_last 	; sentenal block
	mov	di,[di].hi_first	; arena header
	mov	di,[di].la_free_next	; first free block
countloop:
	cmp	di,si
	jz	countexit
	add	ax,[di].la_size 	; count size of this block
	sub	ax,SIZE LocalArenaFree	; less block overhead
	mov	di,[di].la_free_next	; next free block
	jmp	countloop
countexit:
	pop	si
	pop	di
	ret
cEnd nogen



;-----------------------------------------------------------------------;
; LocalHeapSize								;
; 									;
; Return the # bytes allocated to the local heap.			;
; 									;
; Arguments:								;
;	DS = heap segment						;
; 									;
; Returns:								;
;	AX = size of local heap						;
; 									;
; Error Returns:							;
; 									;
; Registers Preserved:							;
;	CX,DX,SI,SI,DS,ES						;
; 									;
; Registers Destroyed:							;
; 	BX								;
;									;
; Calls:								;
; 									;
; History:								;
; 									;
;  Sat Aug 15, 1987 04:35:55p  -by-  Bob Gunderson [bobgu]		;
; Wrote it.								;
;-----------------------------------------------------------------------;


cProc	LocalHeapSize,<PUBLIC,FAR>
cBegin nogen
	mov	bx,pLocalHeap
	mov	ax,[bx].hi_last
	sub	ax,[bx].hi_first
	ret
cEnd nogen


;-----------------------------------------------------------------------;
; LocalHandleDelta							;
; 									;
; Change the number of handles to allocate each time			;
; 	     								;
; Arguments:								;
;	delta = new # of handles or 0					;
;	DS = heap segment      						;
; 									;
; Returns:								;
;	AX = new number of handles					;
; 	     								;
; Error Returns:							;
; 									;
; Registers Preserved:							;
;	CX,DX,SI,SI,DS,ES						;
; 									;
; Registers Destroyed:							;
; 	BX								;
;									;
; Calls:								;
; 									;
; History:								;
; 									;
;-----------------------------------------------------------------------;


cProc	LocalHandleDelta,<PUBLIC,FAR>
	parmW 	delta
cBegin
	mov	bx,pLocalHeap
	mov	ax, delta
	or	ax, ax			; Zero means return present value
	jz	return_present_value
	mov	ax, delta
	mov	[bx].hi_hdelta, ax	; Set new value
return_present_value:
	mov	ax, [bx].hi_hdelta
cEnd
    
sEnd	NRESCODE

end
