Trailing-Edge
-
PDP-10 Archives
-
decuslib20-01
-
decus/20-0003/heap.mac
There are 4 other files named heap.mac in the archive. Click here to see a list.
comment %
HEAP MANAGER
these functions ar based on an algorythm described by Knuth in
"The Art of Computer Programming"
Original work done by Shel Kaphan (SK@SAIL) ca. 1978
Revamped and augmented by Dave Dyer (DDYER@ISIB) ca. 1979.
Adapted for PASCAL usage by Dave Dyer, ca. 1980.
No Rights Reserved.
%
;assumptions made by the Pascal compiler for Pascal use:
; args:
; NEW
; size of block in 2
; DISPOSE
; pointer in 2
; size of block in 3
; sideeffects:
; any memory needed is gotten from the Pascal heap, via GETNEW.
; currently there is no way to return memory to the heap
; AC's 0 to 6 are assumed free for use by NEW and DISPOSE. No others
; are used. Note that the compiler will save AC's 2 to 6 if they
; are active, and will recompute 1 (the display pointer) if it is needed
; parameters, program begins on next page
define params
<.twseg==:1 ; set =0 for oneseg version
search pasunv
.sat==:00 ; 1= stand alone test version
; -1 for quiet test
ifn tops10,<opsys==:-1>
ife tops10,<
ifn tenex,<opsys==:0>
ife tenex,<opsys==:1>
> ;ife tops10
; -1= tops10 version
; 0 = tenex
; 1 = tops20
bakwd==:1 ; 1= allocate from top down, 0=bottom up
;with bakwd on, uses GETNEW in PASIO, with it off,
;conventional allocation at .JBFF.
xsize==:1 ; 1= insist on allocating EXACT size requested.
; 0= otherwise, can be a few words bigger.
; and therefore doesn't check size of returned objects.
nil=377777 ; representation of NIL
pagsiz=777 ;should use 1777 for tops10&kacpu, since that uses K
; instead of pages. However since they also don't do
; auto-expansion but allocate in a fixed space, I
; want to minimize the use of their space.
.clear==:0 ;if nonzero, clear the area
>
params
UNIVERSAL USEDEF
comment *
subtoutines use ac 17 as a stack
take arguments in ac1 ac2 ..
return values in ac1 ac2 ..
do NOT modify any ac that is not a returned value
*
;accumulators
T1=2
T2=T1+1
T3=T2+1
T4=T3+1
T5=T4+1
P=17
if1,<
ifn .sat, <PRINTX STAND ALONE TEST version>
ifl opsys,<printx Tops-10 version>
ife opsys,<printx TENEX version>
ifg opsys,<printx Tops-20 version>
ifn pagsiz-777,<printx Non-standard page size>
ifn bakwd,<printx BACKWARD version>
>
ifn .twseg,< ;macros for oneseg version
if1,<printx TWOSEG version>
define ini(a,b)
< title A B
params
ife opsys,< search stenex>
ifg opsys,< search monsym>
ifn .twseg,<
twoseg
reloc 0
reloc 400000
..loc==:1
>
ife .twseg,<..loc==:0>
pure
entry a
a::>
define pure
<ife ..loc,< reloc>
>
define impure
<ifn ..loc,< reloc>
>
>
ife .twseg,< ;macros for twoseg version
if1,< printx ONESEG version>
define ini
< ..loc==:0
>
define pure<>
define impure<>
>
define typ(msg)
<ifge opsys,<
hrroi 1,msg
psout
>
ifl opsys,<
outstr msg
>
>
define typc(chr)
<ifl opsys,<
outchr chr
>
ifge opsys,<
move 1,chr
pbout
>
>
define error(msg)
<
ifl opsys,<
OUTSTR [ASCIZ/?
? MSG
/]
ifn .sat,<
EXIT 1,
POPJ P,
> ;ifn .sat
ife .sat,<
HRRZ 0,-1(P) ;get return pc
PUSHJ P,RUNER.##
> ;ife .sat
> ;ifl opsys
ifge OPSYS,<
ifn .sat,< PUSH P,1>
HRROI 1,[ASCIZ/?
? MSG
/]
PSOUT
ifn .sat,<
HALTF
POP P,1
POPJ P,
> ;ifn .sat
ife .sat,<
HRRZ 0,-1(P) ;get return pc
PUSHJ P,RUNER.##
> ;ife .sat
> ;ifge opsys
> ;define error
prgend;
SEARCH USEDEF
INI HEAP,<nontrivial heap allocator>
COMMENT %
ENTRY POINTS
NEW call to get a block
MOVEI B,SIZE
PUSHJ P,NEW
RETURN HERE
B=BLOCK, RPART of -1 and N+1 available
DISPOS call to release a block
MOVE B,ADDR or MOVE B,[SIZE,,ADDR]
PUSHJ P,DISPOS
RETURN HERE
CAINIT call to initialize (once only)
RETURN HERE, AC 1 2 3 4 used
CASIZE call to verify ca data base,
return USED,,FREE
ALL with return on success, or trap to pascal if an error is detected.
standard boundary tag heap allocation.
unallocated blocks kept in doubly linked lists.
two words overhead per area
free storage list looks like
word 0: [tag bit][size of block],,[link to next block]
word 1: [link to previous block]
.
.
.
word N-1: [tag bit][size=N] ,,0
when block adjacent to a free area is released, the free area is
unlinked from the avail list temporarily, merged with the newly
released area, and then the whole block is put onto the free list.
%
;
; call with pointer in T1
;
ENTRY DISPOS ;this is what gets it pulled in
ife .sat,< ;for Pascal only
entry dispf. ;special entry for records with files in them
dispf.: pushj p,dispc.##
;jrst dispos ;fall into regular dispose
> ;ife .sat
DISPOS::
ifn .sat,<
PUSH P,T2
PUSH P,T3
PUSH P,T4
>
ifn xsize,<
MOVE T3,T2 ; get (optional) size
>
ANDI T1,-1 ; get adress part only
CAIE T1,NIL ; trying to release nil?
SOSG T2,T1 ; jump if zero being disposed.
PUSHJ P,RLSBXR
; some consistancy checking
HLRE T4,(T2) ; get size of block
JUMPL T4,.+2 ;tag bit must be on!
PUSHJ P,RLSDED ;already killed!
ANDI T4,377777 ;get size
CAIGE T4,3 ;must be at least this big to be real
PUSHJ P,RLSDED ;so spread the bad news
ifn xsize,<
JUMPE T3,RLSC1 ;jump if he didn't claim to know then size
CAIE T3,-2(T4) ;is he as smart as he thought?
PUSHJ P,RLSDED ;no.
>
RLSC1: ADDI T4,(T2) ;ptr to next block
MOVE T3,-1(T4) ;get end tag
XOR T3,(T2) ;compare with begin tag
TLNE T3,-1 ;must match
PUSHJ P,RLSDED ;BAD NEWS!
SKIPGE -1(T2) ;previous block free?
JRST RLSB3 ;no, don't merge to this block.
RLSB2: ;preserve t4
MOVE T1,T2 ;get ptr to current block in T1
HLRZ T3,-1(T2) ;get size of previous block
SUBI T1,(T3) ;get ptr to start of previous block
PUSHJ P,UNLINK ;unlink previous area from avail list
HLRZ T2,(T2) ;get size of current block
HLRZ T3,(T1) ;get size of previous in T4
ADDI T2,(T3) ; (t4 was smashed in UNLINK)
HRLM T2,(T1) ;add to size of previous block
MOVE T2,T1 ;move pointer to previous block
RLSB3: ; t4 preserved
MOVE T1,T4
SKIPGE (T4) ;free?
JRST RLSB5 ;no, don't merge next
PUSHJ P,UNLINK ;unlink next area
HLRZ T3,(T1) ;get size of next
ADD T1,T3 ;first element after merged block
HLRZ T4,(T2) ;size of current
ANDI T4,377777 ;throw out tag bit (or else!)
ADDI T3,(T4) ;sum = size of merged block
HRLM T3,(T2) ;deposit into size of first
RLSB5: ;T1=LAST+1, T2=FIRST-1
HLRZ T4,(T2) ;total size of block to be released.
ANDI T4,377777 ;make sure tag bit off
HRLZM T4,(T2) ;put size, tag in first word
HRLZM T4,-1(T1) ;put size, tag in last word
;this code (hopefully) links the new block to the end of the list
HRRZ T4,AVAIL+1 ;get back link of avail list
HRRM T4,1(T2) ;back link new block to previous end
HRRM T2,(T4) ;forward link old end of list to block
MOVEI T4,AVAIL
HRRM T4,(T2) ;forward link block to avail list head
HRRM T2,1(T4) ;back link list head to current block
SETZ T1,
POPJX:
ifn .sat,<
POP P,T4
POP P,T3
POP P,T2
>
POPJ P,
RLSDED:
ERROR<DISPOSE called with clobbered or already-disposed object>
POPJ P, ;if he continues, ignore error
RLSBXR:
ERROR<DISPOSE called with zero or NIL poiniter>
POP P,(P) ;if he continues, exit dispose
JRST POPJX
;
; allocate a block of heap.
;
;ENTRY NEW ;no entry, so old memory manager can be used
NEW::
ifn .sat,<
PUSH P,T2
PUSH P,T3
PUSH P,T4
>
SKIPE BEGMEM ;init memory if not done yet
JRST GET001
PUSH P,T1 ;preserve size over call to cainit
PUSHJ P,CAINIT
POP P,T1
GET001: SKIPG T2,T1
PUSHJ P,GETBXR
ADDI T2,2
GETRTY: MOVEI T1,AVAIL ;get available list header
GETBF0: HRRZ T3,(T1) ;get link to next element of list
HLRZ T4,(T1) ;get size of this element
CAIG T2,(T4) ;area big enough?
JRST GETBF1 ;yes, allocate out of it
GETB00: HRRZ T1,T3 ;save pointer
CAIE T3,AVAIL ;back to avail list?
JRST GETBF0 ;if not, keep trying.
GETBXX: MOVE T1,T2 ;try memory expansion
PUSH P,T2 ;preserve size over call to .alcor
IFE BAKWD,<
MOVE T2,ENDMEM
>
IFN BAKWD,<
MOVE T2,BEGMEM
>
MOVEM T2,SYSFF##
PUSHJ P,RECALC ;re init for expanded memory
POP P,T2
JRST GETRTY
GETBF1: ;T1 points to block we are allocating out of
;T2 is size of words+2 to allocate
;T3 is link to next free block, if any
;T4 is size of current block
CAILE T2,-3(T4) ;at least 3 words extra?
; Actually, this could be -2(t4)
; but the result would be to leave
; a 2 word block that could never be allocated,
; and would clutter up the free list until
; one of the adjacent blocks was freed.
JRST GETBF2 ;no
ifn .sat,< PUSH P,T5>
MOVEI T5,(T4) ;get size of old free block
ADDI T5,-1(T1) ;get last word of free block
MOVEI T3,(T2) ;get size
ADDI T3,(T1) ;ptr to new free block
SUBI T4,(T2) ;find size of new free block
HRLM T4,(T3) ;store size of new free block
HRLM T4,(T5) ;update size in last word of free block
ifn .sat,< POP P,T5>
HRRZ T4,1(T1) ;find old backlink
HRRM T4,1(T3) ;backlink new area to previous
HRRM T3,(T4) ;also store forward link in previous
HRRZ T4,(T1) ;find old forward link
HRRM T4,(T3) ;deposit it in new area
HRRM T3,1(T4) ;update backlink in next
MOVEI T4,(T1) ;get size of current block
ADDI T4,-1(T2) ;find last location
IORI T2,400000
HRLZM T2,(T1)
HRLZM T2,(T4) ;store tag,size in 1st, last words.
JRST GETBF3
;gets here if whole free block was allocated.
;t4 = size of total free block when coming in here.
GETBF2:
ifn XSIZE,<
CAIE T4,(T2) ;was it an exact match?
JRST GETB00 ;no. Because we allow the size returned to
;be specified, we have to try something
;different. Otherwise, someone will eventually
;complain because the object is a different
;size than was asked for.
>
ADDI T4,-1(T1) ;location of last word
HRLZI T2,400000 ;set up tag bit
IORM T2,(T1) ;turn tag on in first word
IORM T2,(T4) ;also in last
PUSHJ P,UNLINK
HLLZS (T1) ;zero free list link
GETBF3: ; T1 = adress of allocated area's header word
HLLZS (T1) ;clear forward pointer
HLRZ T3,(T1) ;actual size of area
ANDI T3,377777 ;clear allocated bit
ADDI T3,-2(T1) ;last word to clear
HLLZS (T3) ;clear tail pointer
MOVEI T1,1(T1) ;return first available word
ifn .clear,<
SETZM (T1) ;clear allocated area
CAIL T1,(T3) ;was it exaactly 1 word?
JRST POPJX
MOVEI T2,1(T1)
HRLI T2,(T1)
BLT T2,(T3)
>
JRST POPJX
GETBXR: ERROR<NEW called for Zero size!>
MOVEI T1,NIL ;if continued, return NIL
JRST POPJX
; subroutine to unlink block addressed by T1 from free list
; called with PUSHJ P,UNLINK
UNLINK: PUSH P,T4
PUSH P,T3
HRRZ T4,(T1) ;get ptr to next
HRRZ T3,1(T1) ;get ptr to previous
HRRM T3,1(T4) ;unlink from next
HRRM T4,(T3) ;unlink from previous
POP P,T3
POP P,T4
POPJ P,
ENTRY CAINIT
CAINIT::MOVEI T1,3
MOVEI T1,AVAIL
MOVEM T1,(T1)
MOVEM T1,1(T1) ;init free list
MOVEI T1,3
PUSHJ P,.ALCOR##
MOVSI T2,400003
MOVEM T2,(T1)
MOVEM T2,2(T1)
IFN BAKWD,<
; create an endmem and a BEGMEM block
MOVEM T1,ENDMEM
MOVEI T1,3
PUSHJ P,.ALCOR##
MOVSI T2,400003
MOVEM T2,(T1)
MOVEM T2,2(T1)
MOVEM T1,SYSFF
>
MOVEM T1,BEGMEM ;beginning of memory block
SETZ T1,
; enter here when heap expands
RECALC:
IFN BAKWD,<
; allocate as much as required plus the rest of the page
MOVE T2,SYSFF##
SUBM T2,T1
TRZ T1,PAGSIZ ;back up to top of page
SUB T1,SYSFF ;ask for this much..
MOVN T1,T1
CAIGE T1,6 ;but at least enough for two blocks
ADDI T1,PAGSIZ+1 ;so add a page if we wanted too little
PUSH P,T1
PUSHJ P,.ALCOR## ;go getum
HRLZI T2,400003 ;mark as used
MOVEM T2,(T1)
MOVEM T2,2(T1) ;at both ends
POP P,T2
; mark the rest of the block and the old BEGMEM block
IORI T2,400000
MOVSM T2,3(T1)
MOVEM T1,BEGMEM
ADDI T1,-400000(T2)
MOVSM T2,2(T1)
MOVE T1,BEGMEM
ADDI T1,4
ifn XSIZE,<
SETZ T2,
>
JRST DISPOS
>
IFE BAKWD,<
HRRZ T2,SYSFF
ADD T1,T2 ;proposed new size
TRO T1,PAGSIZ ;round up to next page top
SUBI T1,-1(T2) ;T1= size available without expansion
CAIGE T1,6 ;must be room for 2 blocks
ADDI T1,PAGSIZ+1
PUSHJ P,.ALCOR## ;make sure enough heap
MOVE T2,SYSREL##
MOVE T3,T2
SUBI T2,2(T1) ;size of block to free
IORI T2,400000 ;mark as used
HRLZM T2,(T1)
HRLZM T2,-3(T3) ;at both ends
MOVEI T2,400003
HRLZM T2,(T3)
HRLZM T2,-2(T3) ;make an end of memory block
SUBI T3,2
MOVEM T3,ENDMEM
MOVEI T1,1(T1)
ifn XSIZE,<
SETZ T2,
>
JRST DISPOS ;release the free space
>
ENTRY CASIZE
CASIZE::; verify correctness of data base
; calculate size of free space
SKIPN BEGMEM
PUSHJ P,CAINIT
MOVSI T1,3
ifn .sat,<
PUSH P,T2
PUSH P,T3
PUSH P,T4
>
MOVE T3,BEGMEM
MEMLP: HLRZ T4,(T3) ;get area size
MOVE T2,T4
TRZ T4,400000 ;clear tag
ADDI T4,(T3) ;beginning of next area
; T3= begin this area, T4=begin next area
CAMLE T4,ENDMEM
PUSHJ P,BADCOR ;oops!
TSC T2,-1(T4) ;prev tag should match
TRNE T2,-1
PUSHJ P,BADCOR
HLRE T2,(T3)
JUMPG T2,FREBL
HRLZ T2,T2 ;left half for allocated
TLZ T2,400000
JRST ACCUM
FREBL: SKIPL (T4) ;next better be allocated
PUSHJ P,BADCMP
ACCUM: ADD T1,T2 ;accumulate size
MOVE T3,T4 ;step to the next area
CAMGE T3,ENDMEM
JRST MEMLP
RETX:
ifn .sat,<
POP P,T4
POP P,T3
POP P,T2
>
MOVEM T1,1(P) ;return value
POPJ P,
BADCMP: ERROR<free list not compact>
MOVEI T1,0 ;if continued, return 0
JRST RETX
BADCOR: ERROR<heap is messed up>
MOVEI T1,0 ;if continued, return 0
JRST RETX
IMPURE
AVAIL:: BLOCK 2 ;pointer to free heap, inited by cainit
BEGMEM::BLOCK 1 ;begin of memory area, always marked allocated
ENDMEM::BLOCK 1 ;start of memory area, always marked allocated
PURE
PRGEND
;
;TEST PROGRAM FOR HEAP PACKAGE
;
search usedef
ifn .sat,<
ini cortst,<test program for the heap manager>
.request useful
start: move p,[iowd 100,stack]
setzm allocs
pushj p,cainit##
tlp:
ifg .sat,<
typ <[ASCIZ/ /]>
>
pushj p,casize##
ifg .sat,<
pushj p,typoct
typ <[Asciz/
/]>
>
tlpd: pushj p,random
idivi T1,^D100
jumpl T1,FREEIT
ifg .sat,<
typ <[asciz/alloc /]>
>
movm t1,t2
addi t1,1
ifg .sat,<
pushj p,typoct
>
PUSH P,T1
PUSHJ P,NEW##
POP P,T3 ;size
move t2,allocs
movem t2,(t1)
HRLM T3,(T1)
movem T1,allocs
ifg .sat,<
typ <[asciz/ /]>
pushj p,typoct
>
jrst tlp
freeit: move t1,allocs
jumpe t1,tlpd
frel: hrrz t1,(T1)
skipn t1
move t1,allocs
sojg t2,frel
hrrz t2,(T1)
jumpe t2,lasfre
hrrz t3,(T2)
hrrm t3,(T1)
frep:
ifg .sat,<
typ <[asciz/rels /]>
>
hlrz t1,-1(t2)
trz t1,400000
ifg .sat,<
pushj p,typoct
typ <[ASCIZ/ /]>
>
move t1,t2
ifg .sat,<
pushj p,typoct
>
REPEAT 0,<
hlrz t2,-1(t1)
trz t2,400000
hrli t1,-2(t2)
>
HLRZ T2,(T1)
pushj p,DISPOS##
jrst tlp
lasfre: move t2,allocs
hrrz t1,(T2)
movem t1,allocs
jrst frep
entry typoct
entry typdec
typdec::push p,[^D10]
jrst typer
typoct::push p,[^D8]
typer: exch t3,(P) ;save P3, get radix
push p,t1
push p,t2
pushj p,typsub
pop p,t2
pop p,t1
pop p,t3
popj p,
typsub: lshc t1,-^D35
lsh t2,-1 ;vacate sign bit
divi t1,(t3) ;dividend in T1, remainder in T2
hrlm t2,(p) ;save digit
caie t1,0 ;done?
pushj p,typsub ;no, recurse
hlrz t1,(p) ;get digit
addi t1,"0" ;convert to ascii
typc t1 ;type it
popj p, ;return
random: move t1,rab
mul t1,rab2
addi t2,134
addi t1,1231
movem t2,rab2
movem t1,rab
rotc t1,17
xor t1,t2
popj p,
rab: 123457
rab2: 5421312
allocs: block 1
stack: block 100
prgend start>
SEARCH USEDEF
INI .ALCOR,<trivial memory allocator>
; trivial core allocation
; T1 =NUMBER OF WORDS
; T1 returned is pointer, memorty has been cleared
AC1=T1
AC2=T2
ife .sat,<
; get the memory from pascal, then set SYSREL and SYSFF appropriately
.ALCOR:
IFE BAKWD,< PUSH P,T1>
PUSHJ P,GETNEW## ;get it!
PUSH P,T1
IFE BAKWD,<
IORI T1,PAGSIZ
>
IFN BAKWD,<
ANDCMI T1,PAGSIZ
>
MOVEM T1,SYSREL ;current page boundary
IFE BAKWD,<
MOVE T1,(P)
ADD T1,-1(P)
>
MOVEM T1,SYSFF ;next location to use
POP P,T1
POPJ P,
>
ifn .sat,<
.ALCOR:
PUSH P,AC1
PUSH P,AC2
IFE BAKWD,<
ADD AC1,SYSFF
MOVE AC2,SYSREL
CAIG AC1,1(AC2)
>
IFN BAKWD,<
SKIPN AC2,SYSFF
JRST [MOVEI AC2,400000
MOVEM AC2,SYSREL
MOVEM AC2,SYSFF
JRST .+1]
MOVN AC1,AC1
ADD AC1,SYSFF ;add to used location
CAML AC1,SYSREL ;going below the boundary?
>
JRST RETJBF
ifl opsys,< CORE AC1,>
ifge opsys,<
ife bakwd,<
CAILE AC1,777777
>
ifn bakwd,<
CAIGE AC1,PAGSIZ
>
>
JRST CORERR
ifge opsys,<
IFE BAKWD,<
IORI AC1,PAGSIZ
>
IFN BAKWD,<
ANDCMI AC1,PAGSIZ
>
MOVEM AC1,SYSREL
>
MOVE AC1,SYSFF
IFE BAKWD,< ADD AC1,-1(P)>
IFN BAKWD,< SUB AC1,-1(P)>
RETJBF: HRRZ AC2,SYSFF
IFN BAKWD,<
SETZM 1,(AC1)
SOSG -1(P) ;one word?
MOVEM AC1,-1(P) ;save
JRST BLTDON
HRLI AC1,1(AC1)
MOVSS AC1
BLT AC1,-1(AC2)
MOVE AC1,-1(P)
>
IFE BAKWD,<
SETZM (AC2)
SOSG -1(P);one word?
JRST BLTDON
HRLI AC2,1(AC2)
MOVSS AC2
BLT AC2,-1(AC1)
>
BLTDON: ; ac1 = adress to return
POP P,AC2
POP P,(P)
IFE BAKWD,<
EXCH AC1,SYSFF
>
IFN BAKWD,<
MOVEM AC1,SYSFF
>
POPJ P,
CORERR: ERROR<core expansion failed>
HALT .
>
IFE BAKWD,<
ENTRY SYSFF,SYSREL
SYSFF==.JBFF##
SYSREL==.JBREL##
>
IFN BAKWD,<
IMPURE
ENTRY SYSFF,SYSREL
SYSFF:: BLOCK 1
SYSREL::BLOCK 1
PURE
>
LIT
IMPURE
VAR
PURE
end;