;
; Ullrich von Bassewitz, 17.7.2000
;
; Allocate a block from the heap.
;
; void* __fastcall__ malloc (size_t size);
;
;
; C implementation was:
;
; void* malloc (size_t size)
; /* Allocate memory from the given heap. The function returns a pointer to the
;  * allocated memory block or a NULL pointer if not enough memory is available.
;  * Allocating a zero size block is not allowed.
;  */
; {
;     struct freeblock* f;
;     unsigned* p;
;
;
;     /* Check for a size of zero, then add the administration space and round
;      * up the size if needed.
;      */
;     if (size == 0) {
; 	return 0;
;     }
;     size += HEAP_ADMIN_SPACE;
;     if (size < sizeof (struct freeblock)) {
;         size = sizeof (struct freeblock);
;     }
;
;     /* Search the freelist for a block that is big enough */
;     f = _hfirst;
;     while (f && f->size < size) {
;         f = f->next;
;     }
;
;     /* Did we find one? */
;     if (f) {
;
;         /* We found a block big enough. If the block can hold just the
;          * requested size, use the block in full. Beware: When slicing blocks,
;          * there must be space enough to create a new one! If this is not the
;          * case, then use the complete block.
;          */
;         if (f->size - size < sizeof (struct freeblock)) {
;
;             /* Use the actual size */
;             size = f->size;
;
;             /* Remove the block from the free list */
;             if (f->prev) {
;                 /* We have a previous block */
;                 f->prev->next = f->next;
;             } else {
;                 /* This is the first block, correct the freelist pointer */
;                 _hfirst = f->next;
;             }
;             if (f->next) {
;                 /* We have a next block */
;                 f->next->prev = f->prev;
;             } else {
;                 /* This is the last block, correct the freelist pointer */
;                 _hlast = f->prev;
;             }
;
;         } else {
;
;             /* We must slice the block found. Cut off space from the upper
; 	     * end, so we can leave the actual free block chain intact.
; 	     */
;
; 	    /* Decrement the size of the block */
; 	    f->size -= size;
;
; 	    /* Set f to the now unused space above the current block */
; 	    f = (struct freeblock*) (((unsigned) f) + f->size);
;
;         }
;
;         /* Setup the pointer for the block */
;         p = (unsigned*) f;
;
;     } else {
;
;         /* We did not find a block big enough. Try to use new space from the
;          * heap top.
;          */
; 	if (((unsigned) _hend) - ((unsigned) _hptr) < size) {
;             /* Out of heap space */
;             return 0;
;     	}
;
;
; 	/* There is enough space left, take it from the heap top */
; 	p = _hptr;
;        	_hptr = (unsigned*) (((unsigned) _hptr) + size);
;
;     }
;
;     /* New block is now in p. Fill in the size and return the user pointer */
;     *p++ = size;
;     return p;
; }
;


	.importzp    	ptr1, ptr2, ptr3
	.export	     	_malloc

        .include        "_heap.inc"

	.macpack	generic

;-----------------------------------------------------------------------------
; Code

.segment "LIBCODE"
_malloc:
	sta    	ptr1   	       	        ; Store size in ptr1
  	stx	ptr1+1

; Check for a size of zero, if so, return NULL

  	ora	ptr1+1
    	beq	Done		        ; a/x already contains zero

; Add the administration space and round up the size if needed

  	lda	ptr1
       	add	#HEAP_ADMIN_SPACE
     	sta	ptr1
     	bcc	@L1
     	inc	ptr1+1
@L1: 	ldx	ptr1+1
     	bne	@L2
     	cmp	#HEAP_MIN_BLOCKSIZE+1
     	bcs	@L2
     	lda	#HEAP_MIN_BLOCKSIZE
     	sta	ptr1		        ; High byte is already zero

; Load a pointer to the freelist into ptr2

@L2:	lda    	__heapfirst
     	sta	ptr2
       	lda	__heapfirst+1
      	sta	ptr2+1

; Search the freelist for a block that is big enough. We will calculate
; (f->size - size) here and keep it, since we need the value later.

	jmp	@L4

@L3:	ldy	#freeblock::size
       	lda	(ptr2),y
       	sub	ptr1
	tax		   	        ; Remember low byte for later
      	iny		   	        ; Y points to freeblock::size+1
       	lda	(ptr2),y
	sbc	ptr1+1
	bcs    	BlockFound 	        ; Beware: Contents of a/x/y are known!

; Next block in list

      	iny		       	        ; Points to freeblock::next
      	lda	(ptr2),y
      	tax
      	iny			        ; Points to freeblock::next+1
      	lda	(ptr2),y
      	stx	ptr2
      	sta	ptr2+1
@L4:	ora	ptr2
       	bne	@L3

; We did not find a block big enough. Try to use new space from the heap top.

  	lda	__heapptr
  	add	ptr1  		        ; _heapptr + size
       	tay
       	lda	__heapptr+1
  	adc    	ptr1+1
	bcs	OutOfHeapSpace	        ; On overflow, we're surely out of space

       	cmp	__heapend+1
	bne	@L5
	cpy	__heapend
@L5:	bcc    	TakeFromTop
  	beq    	TakeFromTop

; Out of heap space

OutOfHeapSpace:
  	lda 	#0
  	tax
Done:	rts

; There is enough space left, take it from the heap top

TakeFromTop:
	ldx	__heapptr      	        ; p = _heapptr;
	stx	ptr2
	ldx	__heapptr+1
	stx	ptr2+1

	sty	__heapptr      	        ; _heapptr += size;
       	sta	__heapptr+1
  	jmp	FillSizeAndRet	        ; Done

; We found a block big enough. If the block can hold just the
; requested size, use the block in full. Beware: When slicing blocks,
; there must be space enough to create a new one! If this is not the
; case, then use the complete block.
; On input, x/a do contain the remaining size of the block. The zero
; flag is set if the high byte of this remaining size is zero.

BlockFound:
       	bne    	SliceBlock     	        ; Block is large enough to slice
       	cpx    	#HEAP_MIN_BLOCKSIZE     ; Check low byte
       	bcs	SliceBlock 	        ; Jump if block is large enough to slice

; The block is too small to slice it. Use the block in full. The block
; does already contain the correct size word, all we have to do is to
; remove it from the free list.

       	ldy    	#freeblock::prev+1	; Load f->prev
	lda	(ptr2),y
	sta	ptr3+1
	dey
	lda	(ptr2),y
	sta	ptr3
	dey	   	    	        ; Points to freeblock::next+1
	ora	ptr3+1
	beq	@L1   		        ; Jump if f->prev zero

; We have a previous block, ptr3 contains its address.
; Do f->prev->next = f->next

	lda	(ptr2),y    	        ; Load high byte of f->next
    	sta	(ptr3),y    	        ; Store high byte of f->prev->next
    	dey	   	    	        ; Points to next
    	lda	(ptr2),y    	        ; Load low byte of f->next
    	sta	(ptr3),y       	        ; Store low byte of f->prev->next
    	jmp	@L2

; This is the first block, correct the freelist pointer
; Do _hfirst = f->next

@L1:   	lda	(ptr2),y    	        ; Load high byte of f->next
    	sta	__heapfirst+1
    	dey	       	       	        ; Points to next
    	lda	(ptr2),y    	        ; Load low byte of f->next
    	sta	__heapfirst

; Check f->next. Y points always to next if we come here

@L2:	lda	(ptr2),y       	        ; Load low byte of f->next
    	sta	ptr3
    	iny	      		        ; Points to next+1
    	lda	(ptr2),y	        ; Load high byte of f->next
    	sta	ptr3+1
    	iny	      	  	        ; Points to prev
    	ora	ptr3
    	beq	@L3   		        ; Jump if f->next zero

; We have a next block, ptr3 contains its address.
; Do f->next->prev = f->prev

    	lda	(ptr2),y    	        ; Load low byte of f->prev
    	sta	(ptr3),y    	        ; Store low byte of f->next->prev
    	iny	      	    	        ; Points to prev+1
    	lda	(ptr2),y    	        ; Load high byte of f->prev
    	sta	(ptr3),y    	        ; Store high byte of f->prev->next
       	jmp    	RetUserPtr	        ; Done

; This is the last block, correct the freelist pointer.
; Do _hlast = f->prev

@L3:   	lda	(ptr2),y      	        ; Load low byte of f->prev
    	sta	__heaplast
    	iny	       	       	        ; Points to prev+1
    	lda	(ptr2),y      	        ; Load high byte of f->prev
    	sta	__heaplast+1
    	jmp	RetUserPtr     	        ; Done

; We must slice the block found. Cut off space from the upper end, so we
; can leave the actual free block chain intact.

SliceBlock:

; Decrement the size of the block. Y points to size+1.

    	dey	     		        ; Points to size
    	lda	(ptr2),y	        ; Low byte of f->size
    	sub    	ptr1
    	sta	(ptr2),y
    	tax	     		        ; Save low byte of f->size in X
    	iny	     		        ; Points to size+1
    	lda	(ptr2),y	        ; High byte of f->size
    	sbc	ptr1+1
    	sta	(ptr2),y

; Set f to the space above the current block, which is the new block returned
; to the caller.

    	txa	     		        ; Get low byte of f->size
       	add	ptr2
    	tax
    	lda	(ptr2),y	        ; Get high byte of f->size
    	adc	ptr2+1
    	stx	ptr2
    	sta	ptr2+1

; Fill the size and start address into the admin space of the block
; (struct usedblock) and return the user pointer

FillSizeAndRet:
	ldy    	#usedblock::size        ; p->size = size;
	lda	ptr1 		        ; Low byte of block size
	sta	(ptr2),y
	iny	     		        ; Points to freeblock::size+1
	lda	ptr1+1
	sta	(ptr2),y

RetUserPtr:
        ldy     #usedblock::start       ; p->start = p
        lda     ptr2
        sta     (ptr2),y
        iny
        lda     ptr2+1
        sta     (ptr2),y

; Return the user pointer, which points behind the struct usedblock

	lda	ptr2   	       	        ; return ++p;
	ldx	ptr2+1
	add	#HEAP_ADMIN_SPACE
	bcc	@L9
	inx
@L9:	rts

