1
0
mirror of https://github.com/mgcaret/of816.git synced 2024-12-29 02:29:28 +00:00
of816/asm/memmgr.s
2020-01-04 16:25:41 -08:00

639 lines
18 KiB
ArmAsm

; Memory allocation library
; Structure is a linked list beginning at HIMEM and ending at MEM_TOP
; when HIMEM=MEM_TOP, nothing is allocated
; where each entry in the list is <next(4)><flag+chk(2)><space>
; flag+chk is a 16-bit item whose top bit is 1 for in-use, 0 if free
; at some point, the remainder of the word are the low bits of the address of this
; block header in order to serve as a check for heap corruption
; Allocation is selectable from first-fit or best fit (see below) for extant free blocks
; Freeing the bottom block moves HIMEM up. Adjacent free blocks are combined.
; Tunables
SPLIT_THRESH = 8 ; if block can be split and remainder have this many
; bytes + 6 bytes header, split it
MIN_BRK = $400 ; minimum break between DHERE and HIMEM in pages
; Constants
HDR_SIZE = 6
; Allocate XR bytes, return carry set+pointer in AY if successful
; or carry clear+AY=0 if unsuccessful
; trashes WR (has copy of pointer to the block header) and YR
; This uses a first-fit algorithm. If the selected block has at bytes
; remaining after the allocation, the block will be split with the remaining
; space (less 4 bytes) put into the newly following block
.proc _alloc
jsr _chk_himem
bcs grow
jsr _find_free
bcs grow
; reuse, y = 4, A = existing flag word
ora #$8000
sta [WR],y
jsr _split ; split block if possible
bra _rtn_ptr
grow: jsr _grow_heap
bcs :+
ldy #$0004
lda #$8000
sta [WR],y
bra _rtn_ptr
: lda #$00
tay
clc
rts
.endproc
.proc _rtn_ptr
lda WR
clc
adc #.loword(HDR_SIZE)
tay
lda WR+2
clc
adc #.hiword(HDR_SIZE)
sec
rts
.endproc
.proc _split
jsr _blksize
lda YR ; YR = YR(size)-XR(requested size) = remaining space
sec
sbc XR
sta YR
lda YR+2
sbc XR+2
sta YR+2
lda YR ; now see if it's big enough to split
sec
sbc #.loword(SPLIT_THRESH+HDR_SIZE)
ldy YR+2
sbc #.hiword(SPLIT_THRESH+HDR_SIZE)
bmi done ; do not split, it's too small
lda WR ; set YR to be a pointer to the child block
clc ; YR = WR+XR+6
adc XR
sta YR
lda WR+2
adc XR+2
sta YR+2
lda YR ; (now account for header of parent block)
clc
adc #.loword(HDR_SIZE)
sta YR
lda YR+2
adc #.hiword(HDR_SIZE)
sta YR+2 ; ok now YR points to child block
ldy #$04 ; first mark child block free
lda #$0000 ; by zeroing its flags
sta [YR],y
dey
dey ; y = $02
lda [WR],y ; then copy high word next block pointer of parent
sta [YR],y ; into high word next block pointer of child
dey
dey ; y = $00
lda [WR],y ; and then do low word next block pointer
sta [YR],y ; copy to child
lda YR ; then set parent next block pointer to child
sta [WR],y ; low word
iny
iny ; y = $02
lda YR+2 ; high word
sta [WR],y
done: rts
.endproc
; Load SV_HIMEM into WR and compare to MEM_TOP
; return carry set if MEM_TOP >= SV_HIMEM
; carry clear, otherwise
.proc _chk_himem
ldy #SV_HIMEM
lda [SYSVARS],y
sta WR
iny
iny
lda [SYSVARS],y
sta WR+2
; fall-through
.endproc
; See if WR is at (or above) MEM_TOP
; Z and C reflect usual meanings
.proc _chktop
lda WR+2
cmp MEM_TOP+2
bne :+
lda WR
cmp MEM_TOP
: rts
.endproc
; Move WR to the next block, assuming WR points to an existing block
; return via comparison with MEM_TOP
.proc _nextblk
ldy #$00
lda [WR],y
pha
iny
iny
lda [WR],y
sta WR+2
pla
sta WR
bra _chktop
.endproc
; YR = [WR] - WR - 6
.proc _blksize
ldy #$00
lda [WR],y
sec
sbc WR
sta YR
iny
iny
lda [WR],y
sbc WR+2
sta YR+2
lda YR
sec
sbc #$0006
sta YR
lda YR+2
sbc #$0000
sta YR+2
rts
.endproc
.if 0 ; use first-fit if nonzero, best-fit if zero
; Find a free block of at least size XR
; if found, return with its address in WR and carry clear, YR is the block size, A
; is the contents of the flags word, and Y is 4
; otherwise carry set and WR should be equal to HIMEM
.proc _find_free
jsr _chk_himem
bcc lp
rts
next: jsr _nextblk
bcs done
lp: jsr _blksize
lda YR+2
cmp XR+2
bne :+
lda YR
cmp XR
: bcc next ; too big
ldy #$04 ; got one, is it free?
lda [WR],y
bmi next ; nope
clc
done: rts
.endproc
.else
; Find the best-fitting block of at least size XR
; if found, return with its address in WR and carry clear, YR is the block size, A
; is the contents of the flags word, and Y is 4
; otherwise carry set and WR should be equal to HIMEM
; trashes ZR, which holds the size of the best candidate so far
.proc _find_free
stz ZR ; zero out best fit size
stz ZR+2
jsr _chk_himem
bcc :+ ; if we have some heap, go see if anything free
rts ; otherwise just return with carry set
: pha ; make room on stack for "best" candidate
pha
bra lp ; enter loop
best: lda YR ; save block in WR as best block
sta ZR ; starting with size
lda YR+2
sta ZR+2
lda WR ; then with address
sta 1,s
lda WR+2
sta 3,s
next: jsr _nextblk
bcs done ; when we run out of blocks
lp: ldy #$04
lda [WR],y ; is it free?
bmi next ; nope, move on
jsr _blksize
lda YR+2
cmp XR+2
bne :+
lda YR
cmp XR
: bcc next ; too big
lda ZR ; do we have a best candidate so far?
ora ZR+2
beq best ; no, make this one the best
lda ZR+2 ; yes, see if this one is better
cmp YR+2
bne :+
lda ZR
cmp YR
: bcs best ; save as best (prefer higher blocks if =)
bra next ; otherwise go to next block
done: lda ZR
ora ZR+2
beq none
lda ZR
sta YR
lda ZR+2
sta YR+2
pla
sta WR
pla
sta WR+2
ldy #$04
lda [WR],y
clc
rts
none: pla ; drop the block pointer
pla
sec
rts
.endproc
.endif
; YR = WR-XR
.proc _wr_minus_xr
lda WR
sec
sbc XR
sta YR
lda WR+2
sbc XR+2
sta YR+2
rts
.endproc
; Grow heap to store at least XR bytes. Trashes WR and YR.
; return carry clear, HIMEM adjusted, and WR=new HIMEM if grow succeeded
; otherwise carry set and no changes to HIMEM
.proc _grow_heap
jsr _chk_himem
jsr _wr_minus_xr ; calculate bottom of reservation
lda YR ; then subtract header size
sec
sbc #.loword(HDR_SIZE)
sta YR
lda YR+2
sbc #.hiword(HDR_SIZE)
sta YR+2
lda DHERE ; now compare to DHERE+minimum break
clc
adc #.loword(MIN_BRK)
tay
lda DHERE+2
adc #.hiword(MIN_BRK)
cmp YR+2
bne :+
tya
cmp YR
: bcs done ; would put us in the break, byebye
lda YR ; move YR to WR
sta WR
lda YR+2
sta WR+2
ldy #$04 ; offset of flags
lda #$0000
sta [WR],y ; zero them (marked free)
ldy #SV_HIMEM+2 ; now get current HIMEM
lda [SYSVARS],y
pha ; save high byte on stack
dey
dey
lda [SYSVARS],y ; low byte...
ldy #$00
sta [WR],y ; use it to make link
iny
iny
pla
sta [WR],y
ldy #SV_HIMEM+2 ; and set HIMEM to WR
lda WR+2
sta [SYSVARS],y
dey
dey
lda WR
sta [SYSVARS],y
clc
done: rts
.endproc
; Free memory pointed to by WR (at first byte of data, not marker)
; Also trashes XR and YR
; returns carry set if things went fine
; clear if double-free or freeing top of heap
.proc _free
lda WR
sec
sbc #.loword(HDR_SIZE)
sta WR
lda WR+2
sbc #.hiword(HDR_SIZE)
sta WR+2
ldy #$04
lda [WR],y
bpl bad
and #$7FFF
sta [WR],y
jsr _collect
jsr _shrink_heap
sec
rts
bad: clc
rts
.endproc
; Collect adjacent free blocks into larger blocks
; uses WR,XR,YR
.proc _collect
jsr _chk_himem
bcs done
loop: ldy #$04
lda [WR],y
and #$8000
beq :+ ; this block is free, peek at the next block
next: jsr _nextblk ; otherwise, it is used, move on
bcc loop
done: rts
: dey
dey
lda [WR],y ; get next block address into YR
sta YR+2
dey
dey
lda [WR],y
sta YR
lda YR+2
cmp MEM_TOP+2 ; and see if it is MEM_TOP
bne :+
lda YR
cmp MEM_TOP
: bcs done ; if it is, we are done
ldy #$04 ; see if it is a free block
lda [YR],y
and #$8000
bne next ; if not free, move on
dey ; if free, eat it by copying its pointer to ours
dey
lda [YR],y
sta [WR],y
dey
dey
lda [YR],y
sta [WR],y
bra loop ; check this block *again* to roll up more
.endproc
; Shrink the heap by removing the first block, if it is free
.proc _shrink_heap
loop: jsr _chk_himem
bcs done
ldy #$04 ; see if free
lda [WR],y
and #$8000
bne done ; nope, it's used, we are done
dey
dey
lda [WR],y ; get pointer high word
pha ; save on stack
dey
dey
lda [WR],y ; get low word
ldy #SV_HIMEM ; and write it out to HIMEM making it the new HIMEM
sta [SYSVARS],y
iny
iny
pla
sta [SYSVARS],y
bra loop ; go check it again
done: rts
.endproc
; Memory move routines
; move XR bytes of memory from [WR] to [YR]
; Move appropriately based on source and destination
.proc _memmove
lda WR
cmp YR
lda WR+2
sbc YR+2
; now carry is set if WR >= YR, move down in that case, otherwise move up
; fall-through
.endproc
; Move up if carry clear, down if carry set
.proc _memmove_c
php
lda XR ; first, pre-decrement XR
bne :+
dec XR+2
bpl :+
plp
rts ; nothing to move
: dec XR
plp
bcc _memmove_up
; fall-through if carry set
.endproc
; adapted from 6502org.wikidot.com
.proc _memmove_dn
fromh = WR+2
froml = WR
toh = YR+2
tol = YR
sizeh = XR+2
sizel = XR
md7 = ZR
phx
php
lda #$6B00 ; RTL
sta md7+2
lda #$0054 ; MVN
sta md7
sep #$21 ; 8-bit accumulator, set carry
.a8
lda fromh
sta md7+2
lda toh
sta md7+1
lda sizeh
eor #$80 ; set v if sizeh is zero, clear v otherwise
sbc #$01
rep #$30
.a16
.i16 ; already should be...
ldx froml
ldy tol
tya
cmp froml
bcc md3 ; if y < x then $FFFF-x < $FFFF-y
bra md4
.a8
md1: sta sizeh
eor #$80 ; set v if sizeh is zero, clear v otherwise
sbc #$01
cpx #$0000
bne md2 ; if x is not zero, then y must be
inc md7+2
rep #$20
.a16
tya
bne md4
sep #$20
.a8
md2: inc md7+1
rep #$20
.a16
md3: txa
md4: eor #$FFFF ; A xor $FFFF = $FFFF - a
bvc md5 ; branch if sizeh is nonzero
cmp sizel
bcc md6
lda sizel
md5: clc
md6: pha
phb
jsl f:_callzr
plb
pla
eor #$FFFF ; a xor $FFFF = $FFFF - a = -1 - a
adc sizel
sta sizel ; sizel = sizel - 1 - a
sep #$20
.a8
lda sizeh ; update high byte of size
sbc #$00
bcs md1
plp
.a16
.i16
plx
rts
.endproc
.proc _memmove_up
fromh = WR+2
froml = WR
toh = YR+2
tol = YR
sizeh = XR+2
sizel = XR
mu7 = ZR
; first convert start addresses to end addresses
lda WR
clc
adc XR
sta WR
lda WR+2
adc XR+2
sta WR+2
lda YR
clc
adc XR
sta YR
lda YR+2
adc XR+2
sta YR+2
; now start the move
phx
php
lda #$6B00 ; RTL
sta mu7+2
lda #$0044 ; MVP
sta mu7
sep #$21 ; 8-bit accumulator, set carry
.a8
lda fromh
sta mu7+2
lda toh
sta mu7+1
lda sizeh
eor #$80 ; set v if sizeh is zero, clear v otherwise
sbc #$01
rep #$30
.a16
.i16
ldx froml
ldy tol
tya
cmp froml
bcs mu3
bra mu4
.a8 ; a is 8 bits when we branch to mu1!
mu1: sta sizeh
eor #$80 ; set v if size is zero, clear v otherwise
sbc #$01
cpx #$FFFF
bne mu2 ; if x is not $FFFF, then y must be
dec mu7+2
rep #$20
.a16
tya
cpy #$FFFF
bne mu4
sep #$20
.a8
mu2: dec mu7+1
rep #$20
.a16
mu3: txa
mu4: bvc mu5 ; branch if sizeh is nonzero
cmp sizel
bcc mu6
lda sizel
mu5: clc
mu6: pha
phb
jsl f:_callzr
plb
pla
eor #$FFFF ; a xor $FFFF = $FFFF - a = -1 - a
adc sizel
sta sizel ; sizel = sizel - 1 - a
sep #$20
.a8
lda sizeh ; update high byte of size
sbc #$00
bcs mu1
plp
.a16
.i16
plx
rts
.endproc
.proc _callzr
sep #SHORT_A
.a8
pha ; ( -- x )
rep #SHORT_A
.a16
pha ; ( x -- x x x )
pha ; ( x x x -- x x x ah al )
sep #SHORT_A
.a8
lda #$00 ; ZR is in bank 0
sta 5,s ; ( x x x ah al -- 0 x x ah al )
rep #SHORT_A
.a16
tdc
dec a
clc
adc #.loword(ZR) ; calculate actual location of ZR
sta 3,s ; ( 0 x x ah al -- 0 zrh zrl ah al )
pla ; ( 0 zrh zrl ah al -- 0 zrh zrl )
rtl ; ( 0 zrh zrl -- )
.endproc