|
- ; heap.s - deterministic generated-program free-list heap (E05-S01).
- ;
- ; The caller supplies the region; this module does not choose a generated-
- ; program heap address. All routines run in the generated program's current
- ; RAM bank. See src/common/heap.inc for the block layout and API.
- ;
- ; Allocation is first-fit in address order. A free block is split when the
- ; remainder is at least HEAP_MIN_BLOCK. Freeing inserts in address order and
- ; coalesces with the following free block(s) and then with the preceding one.
- ; Every boundary, size, magic, and double-free check is always on.
-
- .setcpu "6502"
- .include "../common/heap.inc"
-
- .export heap_init, heap_alloc, heap_free
- .export heap_base, heap_end, heap_free_head, heap_free_total, heap_error
-
- .segment "ZEROPAGE"
- heap_ptr: .res 2
- heap_ptr2: .res 2
-
- .segment "CODE"
-
- ; A/X = base (A must be 0), Y = pages (1..127). C=1 with HEAP_ERR_ARG on a
- ; malformed region. On success the heap is one free block.
- heap_init:
- sta heap_arg
- stx heap_arg+1
- sty heap_arg+2
- lda #HEAP_OK
- sta heap_error
-
- lda heap_arg+2
- beq @bad
- cmp #HEAP_MAX_PAGES+1
- bcs @bad
- lda heap_arg
- bne @bad
-
- lda heap_arg+1
- clc
- adc heap_arg+2
- bcs @bad
- cmp #$C1 ; heap must end at or below $C000
- bcs @bad
- sta heap_end+1
- lda #$00
- sta heap_end
-
- lda heap_arg+1
- sta heap_base+1
- lda #$00
- sta heap_base
-
- lda heap_base
- sta heap_free_head
- lda heap_base+1
- sta heap_free_head+1
-
- lda #$00
- sta heap_free_total
- lda heap_arg+2
- sta heap_free_total+1
-
- lda heap_base
- sta heap_ptr
- lda heap_base+1
- sta heap_ptr+1
- ldy #0
- lda #$00
- sta (heap_ptr),y ; size low
- iny
- lda heap_arg+2
- sta (heap_ptr),y ; size high = pages
- iny
- lda #$00
- sta (heap_ptr),y ; next low
- iny
- sta (heap_ptr),y ; next high
- clc
- rts
- @bad:
- lda #HEAP_ERR_ARG
- sta heap_error
- sec
- rts
-
- ; A/X = requested payload bytes. Returns A/X = payload, C=0; or A/X=0, C=1
- ; with heap_error. The payload is zeroed.
- heap_alloc:
- sta heap_req
- stx heap_req+1
- lda #HEAP_OK
- sta heap_error
-
- lda heap_req
- clc
- adc #HEAP_HEADER_SIZE
- sta heap_need
- lda heap_req+1
- adc #0
- sta heap_need+1
- bcc :+
- jmp @oom
- :
- lda heap_need
- and #1
- beq @rounded
- inc heap_need
- bne @rounded
- inc heap_need+1
- bne :+
- jmp @oom
- :
- @rounded:
- lda heap_need+1
- bpl :+
- jmp @oom
- :
- bne @min_ok
- lda heap_need
- cmp #HEAP_MIN_BLOCK
- bcs @min_ok
- lda #HEAP_MIN_BLOCK
- sta heap_need
- lda #0
- sta heap_need+1
- @min_ok:
- lda #0
- sta heap_prev
- sta heap_prev+1
- lda heap_free_head
- sta heap_cur
- lda heap_free_head+1
- sta heap_cur+1
-
- @walk:
- lda heap_cur
- ora heap_cur+1
- bne :+
- jmp @oom
- :
- lda heap_cur
- sta heap_ptr
- lda heap_cur+1
- sta heap_ptr+1
- ldy #0
- lda (heap_ptr),y
- sta heap_bsize
- iny
- lda (heap_ptr),y
- and #$7F
- sta heap_bsize+1
-
- lda heap_bsize+1
- cmp heap_need+1
- bcc @next
- bne @found
- lda heap_bsize
- cmp heap_need
- bcc @next
-
- @found:
- lda heap_bsize
- sec
- sbc heap_need
- sta heap_rem
- lda heap_bsize+1
- sbc heap_need+1
- sta heap_rem+1
- lda heap_rem+1
- beq :+
- jmp @split
- :
- lda heap_rem
- cmp #HEAP_MIN_BLOCK
- bcc :+
- jmp @split
- :
- jmp @use_whole
-
- @next:
- lda heap_cur
- sta heap_prev
- lda heap_cur+1
- sta heap_prev+1
- ldy #2
- lda (heap_ptr),y
- sta heap_cur
- iny
- lda (heap_ptr),y
- sta heap_cur+1
- jmp @walk
-
- @use_whole:
- lda heap_cur
- sta heap_block
- lda heap_cur+1
- sta heap_block+1
- lda heap_block
- sta heap_ptr
- lda heap_block+1
- sta heap_ptr+1
- ldy #2
- lda (heap_ptr),y
- sta heap_next
- iny
- lda (heap_ptr),y
- sta heap_next+1
-
- lda heap_prev
- ora heap_prev+1
- beq @uw_head
- lda heap_prev
- sta heap_ptr
- lda heap_prev+1
- sta heap_ptr+1
- ldy #2
- lda heap_next
- sta (heap_ptr),y
- iny
- lda heap_next+1
- sta (heap_ptr),y
- jmp @uw_removed
- @uw_head:
- lda heap_next
- sta heap_free_head
- lda heap_next+1
- sta heap_free_head+1
- @uw_removed:
- lda heap_free_total
- sec
- sbc heap_bsize
- sta heap_free_total
- lda heap_free_total+1
- sbc heap_bsize+1
- sta heap_free_total+1
-
- jsr @mark_used
- lda heap_bsize
- sec
- sbc #HEAP_HEADER_SIZE
- sta heap_zero_len
- lda heap_bsize+1
- sbc #0
- sta heap_zero_len+1
- jmp @zero_return
-
- @split:
- lda heap_cur
- sta heap_block
- lda heap_cur+1
- sta heap_block+1
- lda heap_block
- sta heap_ptr
- lda heap_block+1
- sta heap_ptr+1
- ldy #2
- lda (heap_ptr),y
- sta heap_next
- iny
- lda (heap_ptr),y
- sta heap_next+1
-
- lda heap_block
- clc
- adc heap_need
- sta heap_rem_block
- lda heap_block+1
- adc heap_need+1
- sta heap_rem_block+1
-
- lda heap_rem_block
- sta heap_ptr2
- lda heap_rem_block+1
- sta heap_ptr2+1
- ldy #0
- lda heap_rem
- sta (heap_ptr2),y
- iny
- lda heap_rem+1
- sta (heap_ptr2),y
- iny
- lda heap_next
- sta (heap_ptr2),y
- iny
- lda heap_next+1
- sta (heap_ptr2),y
-
- lda heap_prev
- ora heap_prev+1
- beq @sp_head
- lda heap_prev
- sta heap_ptr
- lda heap_prev+1
- sta heap_ptr+1
- ldy #2
- lda heap_rem_block
- sta (heap_ptr),y
- iny
- lda heap_rem_block+1
- sta (heap_ptr),y
- jmp @sp_linked
- @sp_head:
- lda heap_rem_block
- sta heap_free_head
- lda heap_rem_block+1
- sta heap_free_head+1
- @sp_linked:
- lda heap_need
- sta heap_bsize
- lda heap_need+1
- sta heap_bsize+1
- jsr @mark_used
-
- lda heap_free_total
- sec
- sbc heap_need
- sta heap_free_total
- lda heap_free_total+1
- sbc heap_need+1
- sta heap_free_total+1
-
- lda heap_need
- sec
- sbc #HEAP_HEADER_SIZE
- sta heap_zero_len
- lda heap_need+1
- sbc #0
- sta heap_zero_len+1
- jmp @zero_return
-
- @mark_used:
- lda heap_block
- sta heap_ptr
- lda heap_block+1
- sta heap_ptr+1
- ldy #0
- lda heap_bsize
- sta (heap_ptr),y
- iny
- lda heap_bsize+1
- ora #$80
- sta (heap_ptr),y
- iny
- lda #$5A
- sta (heap_ptr),y
- iny
- lda #$A5
- sta (heap_ptr),y
- rts
-
- @zero_return:
- lda heap_block
- clc
- adc #HEAP_HEADER_SIZE
- sta heap_ptr
- sta heap_ptr2
- lda heap_block+1
- adc #0
- sta heap_ptr+1
- sta heap_ptr2+1
- jsr heap_zero
- lda heap_ptr2
- ldx heap_ptr2+1
- clc
- rts
-
- @oom:
- lda #HEAP_ERR_OOM
- sta heap_error
- lda #0
- tax
- sec
- rts
-
- ; Zero heap_zero_len bytes at heap_ptr. A=0 and is preserved.
- heap_zero:
- ldx heap_zero_len+1
- beq @tail
- ldy #0
- lda #0
- @page:
- sta (heap_ptr),y
- iny
- bne @page
- inc heap_ptr+1
- dex
- bne @page
- @tail:
- ldy #0
- lda #0
- cpy heap_zero_len
- beq @done
- @byte:
- sta (heap_ptr),y
- iny
- cpy heap_zero_len
- bcc @byte
- @done:
- rts
-
- ; A/X = payload pointer. Null is a successful no-op. On success the block is
- ; free and coalesced; on failure C=1 and heap_error is set.
- heap_free:
- sta heap_arg
- stx heap_arg+1
- lda #HEAP_OK
- sta heap_error
- lda heap_arg
- ora heap_arg+1
- bne :+
- jmp @ok
- :
- lda heap_arg
- and #1
- beq :+
- jmp @corrupt
- :
-
- lda heap_arg
- sec
- sbc #HEAP_HEADER_SIZE
- sta heap_block
- lda heap_arg+1
- sbc #0
- sta heap_block+1
- bcs @header_ok
- jmp @corrupt
- @header_ok:
- lda heap_block+1
- cmp heap_base+1
- bcs :+
- jmp @corrupt
- :
- bne @above_base
- lda heap_block
- cmp heap_base
- bcs :+
- jmp @corrupt
- :
- @above_base:
- lda heap_block+1
- cmp heap_end+1
- bcc @in_range
- beq :+
- jmp @corrupt
- :
- lda heap_block
- cmp heap_end
- bcc :+
- jmp @corrupt
- :
- @in_range:
- lda heap_block
- sta heap_ptr
- lda heap_block+1
- sta heap_ptr+1
- ldy #0
- lda (heap_ptr),y
- sta heap_bsize
- iny
- lda (heap_ptr),y
- sta heap_bsize+1
-
- lda heap_bsize+1
- and #$80
- bne :+
- jmp @double
- :
- lda heap_bsize+1
- and #$7F
- sta heap_bsize+1
-
- lda heap_bsize+1
- bne @size_hi
- lda heap_bsize
- cmp #HEAP_MIN_BLOCK
- bcs :+
- jmp @corrupt
- :
- @size_hi:
- lda heap_bsize
- and #1
- beq :+
- jmp @corrupt
- :
-
- lda heap_block
- clc
- adc heap_bsize
- sta heap_tmp
- lda heap_block+1
- adc heap_bsize+1
- sta heap_tmp+1
- bcc :+
- jmp @corrupt
- :
- lda heap_tmp+1
- cmp heap_end+1
- bcc @size_ok
- beq :+
- jmp @corrupt
- :
- lda heap_tmp
- cmp heap_end
- beq :+
- jmp @corrupt
- :
- @size_ok:
- ldy #2
- lda (heap_ptr),y
- cmp #$5A
- beq :+
- jmp @corrupt
- :
- iny
- lda (heap_ptr),y
- cmp #$A5
- beq :+
- jmp @corrupt
- :
-
- ldy #0
- lda heap_bsize
- sta (heap_ptr),y
- iny
- lda heap_bsize+1
- sta (heap_ptr),y
- iny
- lda #0
- sta (heap_ptr),y
- iny
- sta (heap_ptr),y
-
- clc
- lda heap_free_total
- adc heap_bsize
- sta heap_free_total
- lda heap_free_total+1
- adc heap_bsize+1
- sta heap_free_total+1
-
- lda #0
- sta heap_prev
- sta heap_prev+1
- lda heap_free_head
- sta heap_cur
- lda heap_free_head+1
- sta heap_cur+1
-
- @find:
- lda heap_cur
- ora heap_cur+1
- beq @place
- lda heap_cur+1
- cmp heap_block+1
- bcc @advance
- bne @place
- lda heap_cur
- cmp heap_block
- bcc @advance
- bne :+
- jmp @double
- :
- jmp @place
- @advance:
- lda heap_cur
- sta heap_prev
- lda heap_cur+1
- sta heap_prev+1
- lda heap_cur
- sta heap_ptr
- lda heap_cur+1
- sta heap_ptr+1
- ldy #2
- lda (heap_ptr),y
- sta heap_cur
- iny
- lda (heap_ptr),y
- sta heap_cur+1
- jmp @find
-
- @place:
- lda heap_cur
- ora heap_cur+1
- beq @merged_next
- @merge_next:
- lda heap_block
- clc
- adc heap_bsize
- sta heap_tmp
- lda heap_block+1
- adc heap_bsize+1
- sta heap_tmp+1
- lda heap_tmp+1
- cmp heap_cur+1
- bne @merged_next
- lda heap_tmp
- cmp heap_cur
- bne @merged_next
-
- lda heap_cur
- sta heap_ptr
- lda heap_cur+1
- sta heap_ptr+1
- ldy #0
- lda (heap_ptr),y
- clc
- adc heap_bsize
- sta heap_bsize
- iny
- lda (heap_ptr),y
- adc heap_bsize+1
- sta heap_bsize+1
- ldy #2
- lda (heap_ptr),y
- sta heap_next
- iny
- lda (heap_ptr),y
- sta heap_next+1
- lda heap_next
- sta heap_cur
- lda heap_next+1
- sta heap_cur+1
- jmp @merge_next
-
- @merged_next:
- lda heap_block
- sta heap_ptr
- lda heap_block+1
- sta heap_ptr+1
- ldy #0
- lda heap_bsize
- sta (heap_ptr),y
- iny
- lda heap_bsize+1
- sta (heap_ptr),y
- iny
- lda heap_cur
- sta (heap_ptr),y
- iny
- lda heap_cur+1
- sta (heap_ptr),y
-
- lda heap_prev
- ora heap_prev+1
- beq @link_head
- lda heap_prev
- sta heap_ptr
- lda heap_prev+1
- sta heap_ptr+1
- ldy #0
- lda (heap_ptr),y
- sta heap_tmp
- iny
- lda (heap_ptr),y
- sta heap_tmp+1
-
- lda heap_prev
- clc
- adc heap_tmp
- sta heap_rem
- lda heap_prev+1
- adc heap_tmp+1
- sta heap_rem+1
- lda heap_rem+1
- cmp heap_block+1
- bne @link_prev_block
- lda heap_rem
- cmp heap_block
- bne @link_prev_block
-
- ldy #0
- lda heap_tmp
- clc
- adc heap_bsize
- sta (heap_ptr),y
- iny
- lda heap_tmp+1
- adc heap_bsize+1
- sta (heap_ptr),y
- iny
- lda heap_cur
- sta (heap_ptr),y
- iny
- lda heap_cur+1
- sta (heap_ptr),y
- jmp @ok
- @link_prev_block:
- ldy #2
- lda heap_block
- sta (heap_ptr),y
- iny
- lda heap_block+1
- sta (heap_ptr),y
- jmp @ok
- @link_head:
- lda heap_block
- sta heap_free_head
- lda heap_block+1
- sta heap_free_head+1
- jmp @ok
-
- @ok:
- lda #0
- tax
- clc
- rts
- @double:
- lda #HEAP_ERR_DOUBLE
- sta heap_error
- lda #0
- tax
- sec
- rts
- @corrupt:
- lda #HEAP_ERR_CORRUPT
- sta heap_error
- lda #0
- tax
- sec
- rts
-
- .segment "BSS"
- heap_base: .res 2
- heap_end: .res 2
- heap_free_head: .res 2
- heap_free_total: .res 2
- heap_error: .res 1
- heap_arg: .res 3
- heap_req: .res 2
- heap_need: .res 2
- heap_bsize: .res 2
- heap_rem: .res 2
- heap_rem_block: .res 2
- heap_cur: .res 2
- heap_prev: .res 2
- heap_next: .res 2
- heap_block: .res 2
- heap_tmp: .res 2
- heap_zero_len: .res 2
|