//
//  VMEM - virtual memory package
// last edited September 15, 1977  3:10 PM
//
// Copyright Xerox Corporation 1979

	get "vmem.d"

external	// entry procedures
[	LockOnly; LockReloc; LockZero	// (addr, new, flag) -> ok
		// (from assembly code)
	MAPTRAP	// (vpage, wflag, hptr)
	LockCell	// (lvlock[, proc])
	UnlockCell	// (lvlock)
		// for VMEMAUX
	FindFreeBuf
	defaultNoBufsProc
	FlushBufs
	FlushMapStats
	DoLocks
]

external	// entry statics
[	@HASHMAP; @HASHMAPSIZE; @HASHMAPMASK
	MAPSTATBASE
	@MapStatPtr
	SOFTMAPFLAG
	CheckBPTflag
	@ReprobeInc
		// for VMEMAUX
	@HASHMAPSIZE2
	@HASHMAP1
	@HASHMAPTOP
	EMPTYXX
	NAXX
	MapStatProc
	NoBufsProc
	@Bpt; @BptLast
	LockedCells; EndLockedCells; LastLockedCell
]

external	// procedures
[		// O.S.
	MoveBlock; SetBlock; Zero
	Timer
	CallSwat
	Usc
	DoubleAdd
		// ASMAP
	REHASHMAP
		// User-supplied
	CleanupLocks	// ()
	DOPAGEIO	// (VP, core, # of pages, write flag)
	PageGroupBase	// (VP) -> VP
	PageGroupSize	// (VP) -> # of pages & write group flag
	PageGroupAlign	// (VP) -> core alignment mask
	PageType	// (VP) -> new page flag
]

external	// statics
[		// O.S.
	@oneBits
]


static
[	HASHMAP
	HASHMAPSIZE	// MUST BE POWER OF 2
	HASHMAPSIZE2
	HASHMAPMASK
	HASHMAP1
	HASHMAPTOP
	MAPSTATBASE
	MapStatPtr
	SOFTMAPFLAG = false
	EMPTYXX		// HASHX of empty buffers, lshift 8
	NAXX	// HASHX of unavailable buffers, lshift 8
	MapStatProc	// map statistics procedure
	Bpt; BptLast
	NoBufsProc
	LockedCells; LastLockedCell; EndLockedCells
	CheckBPTflag = false
	ReprobeInc = RepInc
	AnyDirty
	LastTrapTime = 0
	TSA = 0; TSA1 = 0
	AgingInterval = 0
	RefLockedCells = true
	LockOnly	// = FalsePredicate
	LockZero	// = TruePredicate
]


//  PROCESS MAP TRAP, CALLED FROM ASSEMBLY CODE

let MAPTRAP(VPG, WFLAG, HPTR) be
[	CleanupLocks()
	if FlushMapStats() then return	// statistics buffer was full
	if Usc(VPG, MinDummyVP) ge 0 then CallSwat("Illegal VP")
	if @HPTR ne 0 then
	 [	// FIRST WRITE TO CLEAN PAGE
	   HPTR>>HM.CLEAN = 0
	   AnyDirty = true
	   return
	 ]

	let ttv = vec 1
	ttv!1 = Timer(ttv)-LastTrapTime
	ttv!0 = 0
	DoubleAdd(lv TSA, ttv)

	let ptype = PageType(VPG, WFLAG)
	let NEWPAGEFLAG = selecton ptype into
	 [ case 1: false
	   case -1: true
	   default: CallSwat("Bad PageType")
	 ]

	let VPAGE = PageGroupBase(VPG)
	let VNPGS = PageGroupSize(VPAGE)
	let ALIGN = PageGroupAlign(VPAGE)

	let WG = 0
	if VNPGS ls 0 then	// this is a write group
	   WG, VNPGS = WGROUPbit, -VNPGS

//  Mark locked pages as referenced
	if RefLockedCells then
	 for I = LockedCells by LCsize to LastLockedCell-LCsize do
	  unless (I>>LC.proc)(I>>LC.addr, 0, false) do
	 [ let hp = HASHMAP1+(Bpt+@(I>>LC.addr) rshift PS)>>BPT.HASHX*2
	   if (@hp & DUMMYbit) eq 0 then @hp = @hp & not NOTREFbit
	 ]

//  FLUSH Buffer

	let tn = nil
	 [ tn = FindFreeBuf(VNPGS, ALIGN, 0)
	   if tn ne 0 break
	   NoBufsProc()
	 ] repeat

	FlushBufs(tn, VNPGS, -1)

//  READ IN NEW PAGE

	let CORE = tn lshift PS
	test NEWPAGEFLAG
	 ifso Zero(CORE, VNPGS lshift PS)
	  ifnot DOPAGEIO(VPAGE, CORE, VNPGS, false)

//  Reorder buffers

	if (TSA ne 0) % (Usc(TSA1, AgingInterval) ge 0) then
	 [ TSA, TSA1 = 0, 0
	   UpdateChain()
	 ]
	ADDTOMAP(tn, VNPGS, VPAGE, (NEWPAGEFLAG % WFLAG? 0, CLEANbit)+WG)

	CheckBPT()

	LastTrapTime = Timer(ttv)

]

and defaultNoBufsProc() be
	CallSwat("No free buffer(s)")

and FlushMapStats() = valof
[	if MAPSTATBASE eq 0 resultis false
	let n = MapStatPtr-MAPSTATBASE
	MapStatPtr = MAPSTATBASE
	// If using the RAM, MapStatProc must reset the
	//   R register itself and return the old value
	if MapStatProc ne 0 then
	 [ let r = MapStatProc(MAPSTATBASE, n)
	   unless SOFTMAPFLAG do n = r
	 ]
	resultis n eq HASHMAP-MAPSTATBASE
]

and getgroup(i, mask, d) = valof
[	while ((HASHMAP+(Bpt+i)>>BPT.HASHX*2)>>HM.FLAGWD & mask) ne 0 do
	   i = i+d
	resultis i
]

and FindFreeBuf(nb, mask, wanted) = valof
[	let tn = @Bpt
	let blk = vec BptSize
	   // blk!i eq 0 means page not reached yet
	   // blk!i gr 0 means buffer blk!i is first of available block
	   // blk!i ls 0 means buffer i begins av. block of length -blk!i
	Zero(blk, BptSize)
	while tn ne 0 do
	 [ let btn = nil
	   if DoLocks(tn lshift PS, 0, false) goto bot1
	   blk!tn = -1
	   btn = mergeblocks(tn, blk)
	   if ((-btn & mask)+blk!btn+nb le 0) & ((HASHMAP+(Bpt+btn)>>BPT.HASHX*2)>>HM.NFPG eq 0) then
	    [ let rtn = btn+(-btn & mask)
	      if (wanted eq 0) % ((wanted ge rtn) & (wanted ls btn-blk!btn)) resultis rtn
	    ]
bot1:	   tn = Bpt!tn & NEXTmask
	 ]
	resultis 0
]

and mergeblocks(otn, blk) = valof
// Available blocks are of four types:
// closed (C), consisting of an integral number of single pages and complete page groups;
// head (H), consisting only of some initial pages of a page group;
// tail (T), consisting only of some final pages of a page group;
// interior (I), consisting only of some interior pages of a page group.
// The following table specifies whether a given pair of adjacent blocks
//   may be merged (gives the type of the merged block),
//   must be left separate (-),
//   or is impossible (?):
//   C  H  I  T
//C  C  -  ?  ?
//H  ?  ?  H  C
//I  ?  ?  I  T
//T  -  -  ?  ?
// It is easy to distinguish block types on the basis of
//   the group bits in their first and last pages:
// C has (~NF,~NL);
// H has (~NF+NL,NF+NL);
// T has (NF+NL,NF+~NL);
// I has (NF+NL,NF+NL).
// When we want to add a page to the set available as candidates for replacement,
//   we first convert it into a one-page block of the appropriate type,
//   and then do merging according to the table above.
// In fact, it is easiest to detect the cases where merging is forbidden (Tx or xH),
//   and merge in all other cases.
[	let tn = otn

[	let bp = blk+tn
	if @bp eq 0 then
	 [ bp = blk+otn
	   resultis (@bp ls 0? otn, @bp)
	 ]
	let tn0 = (@bp ls 0? tn, @bp)
  [	let bp1 = bp+1
	if @bp1 eq 0 goto down
	let tp = Bpt+tn
	if (HASHMAP1!((Bpt+tn0)>>BPT.HASHX*2)&NFPGbit) ne 0 then
	  if ((HASHMAP1!(tp>>BPT.HASHX*2))&NLPGbit) eq 0 then	// Tx, can't merge
	    goto down
	if (HASHMAP1!((tp-@bp1)>>BPT.HASHX*2)&NLPGbit) ne 0 then
	  if ((HASHMAP1!((tp+1)>>BPT.HASHX*2))&NFPGbit) eq 0 then	// xH, can't merge
	    goto down
	// OK to merge
	let bp0 = blk+tn0
	@bp0 = @bp0+@bp1	// add lengths
	let lenm1 = -@bp0-1
	bp0!lenm1 = tn0	// mark top
	otn = tn0+lenm1	// move to top of block
	tn0 = otn+1
   ]
down:	tn = tn0-1
] repeat

]

and RemoveBufs(tn, NPGS) be
// Remove the affected buffers from the chain
[	let lasttn = tn+NPGS-1

	let I, N = 0, NPGS
	 [ let ip = Bpt+I
	   I = ip>>BPT.NEXT
	   while (I ge tn) & (I le lasttn) do
	    [ I = (Bpt+I)>>BPT.NEXT
	      @ip = (@ip & not NEXTmask)+I
	      N = N-1
	      if N eq 0 return
	    ]
	 ] repeat
]

and UpdateChain() be
// Reorder the chain according to use
[	let I = @Bpt
	@Bpt = 0
	let head1 = 0
	let chain0, chain1 = Bpt, lv head1

	 [	let ip = Bpt+I
		let HP = HASHMAP1+(@ip)<<BPT.HASHX*2
		test (@HP & NOTREFbit) eq 0
		ifso	// REFERENCED, PUT ON chain1
		 [ @HP = @HP+NOTREFbit
		   @chain1 = (@chain1 & not NEXTmask)+I	// OK when chain1 = lv head1
		   chain1 = ip
		 ]
		ifnot	// NOT REFERENCED, PUT ON chain0
		 [ @chain0 = (@chain0 & not NEXTmask)+I	// OK even when chain0=Bpt
		   chain0 = ip
		 ]

		I = @ip & NEXTmask

	 ] repeatuntil I eq 0

//  Link the chains.  @Bpt=head0 already

	chain1>>BPT.NEXT = 0
	chain0>>BPT.NEXT = head1
	BptLast = (head1 eq 0? chain0, chain1)-Bpt
]

and ADDTOMAP(tn, NPGS, VP, newbits) be
[ADM
//  RESET THE PAGED-OUT ENTRY
	if NPGS gr 1 then newbits = newbits+NLPGbit

	(Bpt+BptLast)>>BPT.NEXT = tn
	BptLast = tn+NPGS-1
	let N = tn
	while N ls BptLast do
	 [ Bpt!N = N+1
	   MakeMapEntry(VP, N, newbits)
	   newbits = newbits % NFPGbit
	   VP = VP+1
	   N = N+1
	 ]
	Bpt!N = 0
	MakeMapEntry(VP, N, newbits & not NLPGbit)
]ADM

and MakeMapEntry(VP, tn, bits) be
[	let hp = REHASHMAP(VP)
	let bp = Bpt+tn
	test hp eq 0
	 ifso	// page already in core as a side effect of some external call
	   @bp = (@bp & NEXTmask) + EMPTYXX
	 ifnot
	 [ hp>>HM.NKEY = not VP
	   hp>>HM.FLAGWD = (tn-VP) lshift 8 + bits
	   bp>>BPT.HASHX = (hp-HASHMAP) rshift 1
	 ]
]

and corepage(hp) =
	(hp>>HM.FLAGWD+(not hp>>HM.NKEY) lshift 8) rshift 8

and DeleteMapEntry(hp, dostat) be
[	if hp>>HM.DUMMY ne 0 return
	let oldvp = not hp>>HM.NKEY
	let i = corepage(hp)
	let bp = Bpt+i
	@bp = (@bp & NEXTmask) + EMPTYXX
	hp>>HM.NKEY = 0
	 [ hp = hp+ReprobeInc
	   if (hp-HASHMAPTOP) ge 0 then hp = hp-HASHMAPSIZE
	   let key = not hp>>HM.NKEY
	   if key eq -1 break
	   hp>>HM.NKEY = 0
	   let hp1 = REHASHMAP(key)
	   hp1>>HM.NKEY = not key
	   if hp1 ne hp then
	    [ hp1>>HM.FLAGWD = hp>>HM.FLAGWD
	      (Bpt+corepage(hp1))>>BPT.HASHX = (hp1-HASHMAP) rshift 1
	    ]
	 ] repeat
	if dostat then MapStatProc(i, -1, oldvp)
]

and FlushBufs(tn, N, empty) be
// Empty=0 means just mark clean,
//	>0 means also remove from chain,
//	<0 means mark empty
[	if empty then RemoveBufs(tn, N)
	let l = getgroup(tn+N-1, NLPGbit, 1)
	tn = getgroup(tn, NFPGbit, -1)
   [	AnyDirty = false
	let f = 0	// TN of first dirty buffer in sequence, or 0
	let key = nil	// VP of first dirty buffer (i.e. buffer f)
	let lkey = nil	// VP of last dirty buffer
	let grouptn = nil	// TN of first page of group
	for i = tn to l do
	 [
try:	   let locked = DoLocks(i lshift PS, 0, empty)
	   if locked & empty then CallSwat("Can't flush locked page")
	   let HP = HASHMAP+(Bpt+i)>>BPT.HASHX*2
	   let k = not HP>>HM.NKEY
	   let flags = HP>>HM.FLAGWD
	   if (flags&NFPGbit) eq 0 then grouptn = i	// Remember beginning of group
	   test ((flags&CLEANbit) eq 0) & ((f eq 0) % ((k eq lkey+1) & ((flags&NFPGbit) ne 0)))
	   ifso	// first dirty buffer, or in sequence
	    [ if f eq 0 then	// first one, check for a write group
	       test (flags&WGROUPbit) ne 0
	        ifso	// write whole group (by dirtying the rest of it)
	       [ f = grouptn
	         key = k+f-i	// but lkey = k still
	         for j = i+1 to getgroup(i, NLPGbit, 1) do
	            (HASHMAP+(Bpt+j)>>BPT.HASHX*2)>>HM.CLEAN = 0
	       ]
	        ifnot	// just this page
	         f, key = i, k
	      lkey = k
	      if not locked then HP>>HM.FLAGWD = flags+CLEANbit
	    ]
	   ifnot	// end of sequence
	    [ if f ne 0 then
	       [ flushout(key, f, i)
	         f = 0
	         goto try
	       ]
	    ]
	   if empty then DeleteMapEntry(HP, (empty ls 0? flags&CLEANbit, false))
	 ]
	if f ne 0 then flushout(key, f, l+1)
   ] repeatwhile AnyDirty
	CheckBPT()
]

and flushout(key, f, lim) be
[	let lock = f lshift PS
	LockCell(lv lock)	// in case of recursive VMEM call
	DOPAGEIO(key, lock, lim-f, true)
	UnlockCell(lv lock)
]

and LockCell(lvLock, proc; numargs n) = valof
[	if (n ls 2) % (proc eq 0) then proc = LockOnly
	UnlockCell(lvLock)
	if LastLockedCell eq EndLockedCells then CallSwat("Lock list full")
	LastLockedCell>>LC.addr, LastLockedCell>>LC.proc = lvLock, proc
	LastLockedCell = LastLockedCell + LCsize
]

and UnlockCell(lvLock) = valof
[	for I = LockedCells by LCsize to LastLockedCell-LCsize do
	  if I>>LC.addr eq lvLock then
	    [ LastLockedCell = LastLockedCell - LCsize
	      MoveBlock(I, LastLockedCell, LCsize)
	      resultis true
	    ]
	resultis false
]

and DoLocks(addr, newpa, flag) = valof
[	let oldpa = addr & (not WM)
	let I = LockedCells
	LastLockedCell>>LC.addr = lv addr
	LastLockedCell>>LC.proc = dlexit
	 [ if (@(I>>LC.addr) & (not WM)) eq oldpa then
	    [ let addr = I>>LC.addr	// in case lock proc unlocks cell
	      let new = (newpa eq 0? 0, @addr+newpa-oldpa)
	      unless (I>>LC.proc)(addr, new, flag) resultis true
	      if flag then @addr = new
	    ]
	   I = I+LCsize
	 ] repeat
dlexit:	resultis false
]

// and LockOnly(addr, new, flag) = false

and LockReloc(addr, new, flag) = new ne 0

// and LockZero(addr, new, flag) = true


//
// Checker for BPT
//

and CheckBPT() be
	compileif false then	// ***
[
	unless CheckBPTflag return
	for hp = HASHMAP by 2 to HASHMAPTOP-2 do
	 if hp>>HM.NKEY ne 0 then
	   if (Bpt+corepage(hp))>>BPT.HASHX*2 ne (hp-HASHMAP) then
	      CallSwat("BPT wrong")
	for bp = Bpt+1 to Bpt+#377 do
	 [ let hp = HASHMAP+bp>>BPT.HASHX*2
	   if (hp>>HM.DUMMY eq 0) & (corepage(hp) ne (bp-Bpt)) then
	      CallSwat("BPT wrong")
	 ]
]