Google
 

Trailing-Edge - PDP-10 Archives - decus_20tap2_198111 - decus/20-0057/pfhsno.mac
There are 2 other files named pfhsno.mac in the archive. Click here to see a list.
00100	
00200		TITLE PFHSNO
00300	
00400		ENTRY S$$PFZ
00500	
00600		SEARCH UUOSYM
00700		SEARCH S$$NDF
00800		SALL
00900	
01000	
01100	;*******************************************************************
01200	;
01300	;DECSYSTEM-10 PAGE FAULT HANDLER.....MAY 1977
01400	;
01500	;REVISED VERSION JULY 1978
01600	;ADAPTED FOR FASBOL LIBRARY  AUGUST 1978
01700	;
01800	;
01900	;NIGEL DERRETT
02000	;DATALOGISK AFDELING, MATEMATISK INSTITUT, AARHUS UNIVERSITY, DENMARK
02100	;
02200	;********************************************************************
02300	;
02400	;********************************************************************
02500	;
02600	;This page-fault handler uses a sort of "second chance" algorithm.
02700	;Any pages which have not been accessed for 2 time-slots are regarded
02800	;as candidates to be shoved out
02900	;
03000	;Fasbol programs overwrite the last 1k of core, so the standard
03100	;page fault handler cannot be used.
03200	;This handler should be loaded with the user program. It removes
03300	;the system pfh, if present.
03400	;
03500	;Non-VM systems should set the switch P$VM in SDDNDF to zero;
03600	;VM systems should set it to 1.
03700	;Systems with both VM and non-VM users will need two versions of FASBOL
03800	;and FASLIB.
03900	;
04000	;To initialise the page fault handler:
04100	;	JSR S$$PFZ##
04200	;No regs are altered.
04300	;
04400	;****************************************************************************
04500	
04600	
04700	PAGE
04800	;**********************************************************************
04900	;
05000	; DUMMY MODULE FOR NON-VM SYSTEMS
05100	;
05200	;**********************************************************************
05300	
05400	IFE P$VM<
05500			; dummy code for non-vm systems
05600	
05700		S$$PFZ:	0
05800			JRSTF  @ S$$PFZ
05900	>
06000	
06100	
06200	IFN P$VM<
06300	
06400			; rest of pfh
06500	
06600	PAGE
06700	;**********************************************************************
06800	;
06900	; FEATURES
07000	;
07100	;**********************************************************************
07200	
07300		FTTEST=0	; switch for producing trace output
07400		FTSHOW=0	; switch for producing "hello" message
07500		FTNOTM=0	; switch to disable time interrupts
07600				; (useful for debugging)
07700	
07800	
07900	PAGE
08000	;************************************************************************
08100	;
08200	; CONSTANTS
08300	;
08400	;************************************************************************
08500	
08600	; Registers
08700		T1=1
08800		T2=2
08900		T3=3
09000		T4=4
09100		PN=5	; page number
09200		P=6	; stack pointer
09300	
09400		; ***NOTE***
09500		; The entry code at START requires the regs to be in this order
09600		; GETIN, CREATE, and ACCESS require that PN = T4+1
09700		; Others require that T1-T4 are consecutive.
09800	
09900	
10000	; Constants
10100	
10200	IFN FTNOTM<
10300		TIMINT=0		; no time traps
10400	>
10500	IFE FTNOTM<
10600		TIMINT=1000		; interval between time traps
10700	>
10800	
10900		FFLSTA=2		; initial force level
11000	
11100		MAPSIZE=17		; size of a page map
11200		PAGSIZ=1000		; size of a page
11300	
11400		ALSIZE=20		; size of argument list for CLRABS
11500	
11600	IFN FTTEST,<
11700		STKSIZ=40		; large stack when testing - for OUTNUM
11800	>
11900	IFE FTTEST,<
12000		STKSIZ=20		; small stack normally
12100	>
12200	
12300	
12400	PAGE
12500	;**********************************************************************
12600	;
12700	; MACROS
12800	;
12900	;**********************************************************************
13000	
13100	
13200	DEFINE NOOP <JFCL 0,0>
13300	
13400	
13500	IFN FTTEST,<
13600	DEFINE TRACE(AC, TEXT)<
13700	IFN AC,<	IFN AC-T1 < EXCH T1, AC >
13800			PUSHJ P, OUTNUM
13900			IFN AC-T1,< EXCH T1, AC >
14000		>
14100		OUTSTR [ASCIZ/TEXT
14200	/]
14300	>> ; end IFN FTTEST
14400	
14500	
14600	IFE FTTEST,<
14700		DEFINE TRACE(AC,TXT)<>
14800	>  ; end IFE FTTEST
14900	
15000	
15100	IFN FTTEST,<
15200	DEFINE ERROR(TEXT)<
15300		JRST [	OUTSTR ERMST
15400			OUTSTR [ASCIZ/TEXT
15500	/]
15600			HALT .
15700		     ]
15800	>> ; end IFN FTTEST
15900	
16000	
16100	IFE FTTEST,<
16200	DEFINE ERROR(TEXT)<
16300		HALT .
16400	>> ; end IFE FTTEST
16500	
16600	
16700	DEFINE MESSAGE(TEXT)<
16800		OUTSTR [ASCIZ/TEXT
16900	/]
17000	>
17100	
17200	
17300	PAGE
17400	;*****************************************************************
17500	;
17600	; ENTRY POINT FOR HANDLER WHEN A PAGE FAULT OCCURS
17700	;
17800	;*****************************************************************
17900	
18000	
18100	; Here starteth the handler proper
18200	
18300	PFHSTR:	JRST START
18400	FPC:	0			; PC of page fault word
18500	PFWD:	0			; page fault word
18600	VTIME:	0			; virtual time elapsed
18700	PGRATE:	0			; page rate
18800	PSIVEC:	0			; PSI vectors
18900		BLOCK 3			; reserved by DEC
19000	
19100	
19200	; Normal entry point
19300	START:	SOSE  SEMAFO		; check whether we are in the PFH already
19400	START1:	ERROR<START1>		; yes - error - the handler is not reentrant
19500	
19600		DMOVEM T1,SAVT1		; save accumulators which we will use
19700		DMOVEM T3,SAVT3		;
19800		DMOVEM PN,SAVPN		;
19900		MOVE P, STKWD		; Set up stack
20000	
20100		SKIPGE PFWD		; has WS changed ?
20200		PUSHJ P, REINIT		; yes - reinitialize
20300	
20400	DISPCH:	HRRZ T1,PFWD		; get cause
20500		TRACE T1,<Cause of page fault>
20600	
20700		SOJE T1, DISP2		; Unless it is ALLOW-ACCESS
20800		; get an up-to-date version of the USEDTHISTIME map
20900		MOVE T2, [XWD .PAGGA, UTMBLK]
21000		PAGE. T2,		; get access allowed map
21100	STAER2:	ERROR<STAER2>		; nasty!
21200	
21300	DISP2:	; T1 = faulttype-1
21400		CAIGE T1, 4		; if cause is ZERO-PAGE
21500		CAIN T1, 1		; or non-uuo page not in core
21600		SETOM LASTPC		; then forget LASTPC
21700					; We are actually doing something useful
21800					; so we can't be in a dump-mode IO loop.
21900	
22000		LDB PN,[POINT 17,PFWD,17]; get page number for fault
22100		TRACE PN,<Fault page number>
22200	
22300		PUSHJ P, TABLE(T1)	; dispatch using fault type (T1 = faulttype-1)
22400	
22500		;back here after handliing fault
22600		DMOVE T1, SAVT1		; restore regs
22700		DMOVE T3, SAVT3		;
22800		DMOVE PN, SAVPN		;
22900		AOS SEMAFO		; reset semaphore
23000		JRSTF @FPC		; return to program
23100	
23200	TABLE:	JRST ACCESS		; access not allowed
23300		JRST GETIN		; page not in core
23400		JRST GETUUO		; UUO argument not in core
23500		JRST TIME		; time trap
23600		JRST CREATE		; zero page
23700		JRST CREATE		; UUO zero page	
23800	
23900	
24000	
24100	PAGE
24200	;**********************************************************************
24300	;
24400	; INITIALISATION
24500	;
24600	;**********************************************************************
24700	
24800	S$$PFZ:	; JSR here to initialise pfh
24900		0
25000	
25100		; Save regs
25200		DMOVEM T1, SAVT1
25300		DMOVEM T3, SAVT3
25400		DMOVEM PN, SAVPN
25500		MOVE P, STKWD		; set up stack
25600	
25700		TRACE 0,<Initialising PFH>
25800	
25900		IFE FTTEST<
26000		   IFN FTSHOW< MESSAGE<Fasbol PFH setup>
26100		>>
26200	
26300		; set up the bit maps
26400	
26500		; first get the WS
26600		MOVE T1, [XWD .PAGWS, INMBLK]
26700		PAGE. T1,		; get WS
26800	INITE1:	ERROR<INITE1>		; should never happen
26900	
27000		; now set up the others
27100		MOVEI T1, MAPSIZE-1	; T1 is index into bitmap vectors
27200	INIT1:	SETOM , ULTMAP(T1)	; set USEDLASTTIME map
27300		SOJGE T1, INIT1		; and loop
27400		; We mark all pages as USED-LAST-TIME so that they
27500		; don't get shoved out during the first timeslice.
27600	
27700		; USEDTHISTIME map is set up when it is needed.
27800	
27900		; set up flags
28000		MOVEI T1, FFLSTA	; initial force level
28100		MOVEM T1, FORCFL	; initialise force level
28200		SETOM  LASTPC		; LASTPC := nothing
28300		SETZM DMPADS		; not doing dump-mode IO
28400		SETZM SEMAFO		; initialise semaphore
28500	
28600		; ensure that I am in core before I take over from the system PFH.
28700		MOVEI T1, PFHSTR	; address of first word
28800	INITA:	MOVE T2, (T1)		; access word - ensure that page is in core
28900		ADDI T1, PAGSIZ		; bump up address to point to next page
29000		CAIG T1, PFHEND		; all done?
29100		JRST INITA		; no - check next page
29200		MOVEI T1, PFHEND	; get end of loop right - check end addr is OK
29300		MOVE T2, (T1)		; access word
29400	
29500		; set up .JBPFH word in jobdat area to tell monitor we're here
29600		MOVE T1, [XWD PFHEND,PFHSTR]
29700		MOVEM T1, .JBPFH
29800	
29900		; remove old pfh from last 1K (if it is there)
30000		MOVE T1, [XWD .PAGCD, RPFLS1]	; set up call
30100		PAGE. T1,		; destroy page 776
30200		JRST [			; error return
30300			TRACE T1, <Error code from 776>
30400			JRST INIT2
30500		     ]
30600	INIT2:	MOVE T1, [XWD .PAGCD, RPFLS2]	; set up call
30700		PAGE. T1,		; destroy page 777
30800		JRST [			; error return
30900			TRACE T1, <Error code from 777>
31000			JRST INIT3
31100		     ]
31200	INIT3:
31300		
31400		; set up incore map again - without old pfh
31500		PUSHJ P, REINIT
31600	
31700		; clear access bits for incore pages
31800		PUSHJ P, CLRABS
31900	
32000		; set up timing interval
32100		MOVEI T1, TIMINT	; get timing interval
32200		HRLI T1, 26		; set up call of SETUUO
32300		SETUUO T1,		; set timing interval
32400	STERR:	ERROR<STERR>		; oops!
32500	
32600		DMOVE T1, SAVT1		; restore regs
32700		DMOVE T3, SAVT3
32800		DMOVE P,  SAVPN
32900		AOS SEMAFO		; restore semaphore
33000		JRSTF  @S$$PFZ		; return to caller
33100	
33200	; end of INIT code
33300	
33400	
33500	PAGE
33600	;************************************************************************
33700	;
33800	; REINIT
33900	;
34000	;************************************************************************
34100	
34200	REINIT:	; subroutine
34300		; Who's been eating my porridge ?
34400		; Either the user or the monitor has altered the working set
34500		; (the PF.HCB bit was set).
34600		; This subroutine resets the INCORE bitmap
34700		; nonskip return
34800	
34900		TRACE 0,<WS changed - resetting>
35000	
35100		MOVE T1, [XWD .PAGWS, INMBLK]	; set up call
35200		PAGE. T1,		; INMAP := working set
35300	REERR:	ERROR<REERR>		; HELP!
35400		POPJ P,			; That's all folks!
35500	
35600	; end of subroutine REINIT
35700	
35800	
35900	PAGE
36000	;*********************************************************************
36100	;
36200	; GETIN
36300	;
36400	;*********************************************************************
36500	
36600	GETIN:	; subroutine to get a page into core
36700		; page number in PN
36800		; nonskip return
36900	
37000		TRACE PN,<Fault on page>
37100	
37200		PUSHJ P, OUTPAG		; start by shoving somebody out
37300		NOOP			; nobody got shoved out, but never mind
37400					; we will try to increase the incore size
37500	GETIN1:	MOVEI T4, 1		; put in arg for PAGE UUO
37600					; pageno is in PN
37700					; requires that T4 and PN are consecutive regs
37800		MOVE T1, [XWD .PAGIO, T4]  ; set up call
37900		PAGE. T1,		; get page into core
38000		JRST GETFC		; didn't suceed - force a page out
38100	
38200		; we have now got the page in - by fair means or foul
38300		TRACE PN,<In OK>
38400	
38500		; now set the bit in the INCORE map,
38600		PUSHJ P, MARKIN		;
38700	
38800		; allow access to the page
38900		JRST ACCESS		; and return direct to caller
39000	
39100	GETFC:	; Here if we couldn't get the page in
39200		; error code in T1
39300		PUSHJ P, FORCE		; force a page out
39400		JRST GETIN1		; and try again
39500	
39600	
39700	; end of subroutine GETIN
39800	
39900	
40000	PAGE
40100	;**********************************************************************
40200	;
40300	; CREATE
40400	;
40500	;**********************************************************************
40600	
40700	CREATE:	; Subroutine to create a new page
40800		; pageno in PN
40900		; nonskip return
41000	
41100		TRACE PN,<Create page>
41200	
41300		; unlike GETIN we don't shove a page out first
41400		; we assume that the user is growing.
41500	CREA1:	MOVEI T4, 1		; put in argument for PAGE UUO
41600					; pageno is in PN
41700					; requires that T4 and PN are consecutive regs
41800		MOVE T1, [XWD .PAGCD, T4]  ; set up call
41900		PAGE. T1,		; create page in core
42000		JRST CFORC		; didn't succeed - force a page out
42100	
42200		; we have now created the page - by fair means or foul
42300		TRACE PN,<Created OK>
42400	
42500		; now set the bit in the INCORE map,
42600		; we don't set access allowed, since he may be grabbing a large vector
42700		; and he may not use it for a while.
42800		JRST MARKIN		; and return direct to caller
42900	
43000	
43100	CFORC:	; here if we weren't able to create a new page
43200		; T1 contains error code from PAGE UUO.
43300	
43400		; find out what went wrong
43500		CAIN T1, PAGNS%		; no more swapping space ?
43600		JRST PFHNMS		; yes
43700		PUSHJ P, FORCE		; no - try forcing a page out
43800					; T1 = error code
43900		JRST CREA1		; try again
44000	
44100	; end of subroutine CREATE
44200	
44300	PAGE
44400	;****************************************************************************
44500	;
44600	; FORCE
44700	;
44800	;****************************************************************************
44900	
45000	FORCE:	; subroutine to force pages out in order to get one in, or to
45100		; create a new one.
45200		; on entry  T1 = error code from PAGE. UUO
45300		; nonskip return
45400		TRACE 0,<Forcing>
45500	
45600		CAIE T1, PAGLE%		; did we fail because of not enough
45700					; physical core?
45800	FORCER:	ERROR<FORCER>		; no - then shoving out pages won't help.
45900	
46000		; yes - shove out page and try again
46100	
46200	FORC1:	PUSHJ P, OUTPAG		; shove out a page
46300		SKIPA			; didn't succeed - continue
46400		POPJ P,			; page shoved out - return
46500	
46600		; there are no more candidate pages at this level of forcing
46700		; try more brutal methods
46800		SOSL FORCFL		; reduce forcing level
46900		JRST FORC1		; and loop unless it was already minimum
47000	
47100		; we are at the lowest level of forcing, and still we
47200		; can't find a candidate page
47300		; This user is just too small
47400		PUSHJ P, TOSMAL		; tell him the bad news
47500		; if he continues, then he may have got more core
47600		; so let the calling routine try again
47700		POPJ P,
47800	
47900	; end of subroutine FORCE
48000	
48100	PAGE
48200	;**********************************************************************
48300	;
48400	; TOSMAL
48500	;
48600	;*********************************************************************
48700	
48800	TOSMAL: ; Subroutine called when user hasn't got enough physical core
48900		; Normal return if user does a CONTINUE
49000	
49100	PFHNEP:	MESSAGE<?PFHNEP - NOT ENOUGH PHYSICAL CORE>
49200		HALT .+1		; stop, and give him a chance to do
49300					; something about it
49400		; if he continues, then he may have got more core
49500		; so return to caller
49600		POPJ P,
49700	
49800	; end of subroutine TOSMAL
49900	
50000	
50100	PAGE
50200	;**********************************************************************
50300	;
50400	; PFHNMS
50500	;
50600	;**********************************************************************
50700	
50800	PFHNMS:	MESSAGE<?PFHNMS - NO MORE VM SWAPPING SPACE AVAILABLE>
50900		HALT .			; stop
51000	
51100	
51200	PAGE
51300	;**********************************************************************
51400	;
51500	; MARKIN
51600	;
51700	;**********************************************************************
51800	
51900	MARKIN:	; subroutine
52000		; here if we have successfully got a page into core
52100		; This subroutine marks the page in the INCORE map.
52200		; nonskip return
52300	
52400		MOVE T1, PN		; T1 := page number
52500		PUSHJ P, GETBIT 	; T1 := index into bitmap, T2 := bit
52600		IORM T2, INMAP(T1)	; set bit in INCORE map
52700		POPJ P,			; return
52800	
52900	; end of subroutine MARKIN
53000	
53100	
53200	PAGE
53300	;*************************************************************************
53400	;
53500	; ACCESS
53600	;
53700	;*************************************************************************
53800	
53900	ACCESS: ; subroutine to allow access to a page
54000		; PN = page number
54100		; nonskip return
54200	
54300		TRACE PN,<Allow access to page>
54400	
54500		MOVEI T4, 1		; put in arg for PAGE UUO
54600					; PN is pageno
54700					; requires that T4 and PN are consecutive regs
54800		MOVE T1, [XWD .PAGAA, T4]  ; set up call
54900		PAGE. T1,		; allow acess to page
55000	ACERR:	ERROR<ACERR>		; help!!
55100		POPJ P,			; all done
55200	
55300	; end of subroutine ACCESS
55400	
55500	
55600	PAGE
55700	;************************************************************************
55800	;
55900	; OUTPAG and OUTPGN
56000	;
56100	;************************************************************************
56200	
56300		; Subroutines to shove a candidate page out
56400		; OUTPAG has no parameters, and starts looking for a candidate
56500		; at page 1
56600		; OUTPGN starts looking at pageno in T1
56700		; Skip return if OK, nonskip return if there were no candidates left.
56800		; all regs smashed
56900	
57000	OUTPAG:	MOVEI T1, 1		; OUTPAG starts looking at page 1
57100	OUTPGN:				; OUTPGN starts lookin at pageno in T1
57200		TRACE 0,<Shove someone out>
57300		MOVE T2, FORCFL		; param for FINPAG
57400		PUSHJ P, FINPAG		; find a candidate page
57500		POPJ P,			; no candidates - nonskip return
57600	
57700		; T1 contains the number of the page we are going to shove out
57800		TRACE T1,<Shove out page>
57900		MOVE T4, T1		; T4 := pageno
58000		HRLI T4, 400000		; set bit 0 (direction bit for PAGE UUO)
58100		MOVEI T3, 1		; put in argument for PAGE UUO
58200		MOVE T2, [XWD .PAGIO, T3]  ; set up call
58300		PAGE. T2,		; shove out page
58400		JRST OUTER		; didn't succeed - investigate
58500	
58600		; we have shoved the page out OK.
58700		; T1 = pageno
58800		TRACE T1,<Shoved out OK>
58900		PUSHJ P, GETBIT		; T1 = index, T2 = bit
59000		ANDCAM T2, INMAP(T1)	; clear bit in INCORE map
59100		MOVEI T1, (T4)		; T1 := pageno
59200		JRST SKPRET		; return
59300	
59400	OUTER:	; here if we couldn't shove the page out - PAGE UUO failed
59500		; error code is in T2
59600		CAIN T2, PAGNS%		; VM swapping space used up?
59700		JRST PFHNMS		; yes
59800		CAIE T2, PAGSH%		; was the page in a sharable hiseg ?
59900	OUTER1:	ERROR<OUTER1>		; no - then I don't know what to do
60000	
60100		; yes -the hiseg is sharable.
60200		; Make note, so that we don't bother to look at it again
60300		TRACE 0,<Sharable Hiseg>
60400		MOVEI T1, 377		; pageno of highest page in lowseg
60500		MOVEM T1, TOPPAG	; update TOPPAG
60600		POPJ P,			; no candidates - nonskip return
60700	
60800	; end of subroutine OUTPAG
60900	
61000	PAGE
61100	;**********************************************************************
61200	;
61300	; TIME
61400	;
61500	;**********************************************************************
61600	
61700	TIME:	; Subroutine
61800		; We come here at regular intervals, determined by a SETUUO
61900		; at initialisation time.
62000		; Do accouting.
62100		; throw out all candidate pages which have not been used in
62200		; this timeslice.
62300	
62400		TRACE 0,<Time>
62500	
62600		MOVEI T1, FFLSTA
62700		CAME  T1, FORCFL	; were we forcing?
62800		JRST  [	MOVEM T1, FORCFL	; yes - reset force level
62900			JRST TIM2		; and don't shove more pages out
63000		      ]
63100	
63200		; remove all candidate pages which haven't been shoved out yet
63300		MOVEI T1, 1		; start looking at page 1
63400	TIM1:	PUSHJ P, OUTPGN		; shove a page out
63500		SKIPA			; none left - break out of loop
63600		JRST TIM1		; loop
63700	
63800	TIM2:	; set up usedlasttime map
63900		; USEDLASTTIME := USEDTHISTIME
64000		HRLI T1, UTTMAP
64100		HRRI T1, ULTMAP
64200		BLT T1, ULTMAP+MAPSIZE-1
64300	
64400		; clear access bits for incore pages
64500		JRST CLRABS		; (and return direct to caller)
64600	
64700	; end of subroutine TIME
64800	
64900	
65000	PAGE
65100	;**********************************************************************
65200	;
65300	; CLRABS
65400	;
65500	;*********************************************************************
65600	
65700	CLRABS:	; Subroutine to clear access bits for incore pages
65800		; This means that we can find out which pages have been
65900		; accessed during each time interval, and thus we can
66000		; determine a (hopefully) sensible working set.
66100		;
66200		; We only clear access bits for potential candidates for
66300		; shoving out.
66400		;
66500		; nonskip return
66600	
66700		TRACE 0,<Clear access bits>
66800	
66900		SETZM  ALIST 		; init alist count
67000	
67100		MOVEI T1, 1		; starting page for FINPAG
67200	
67300	CLR1:	MOVEI T2, 0		; force level - INCORE
67400		PUSHJ P, FINPAG		; find a candidate page
67500					; T1 = last page found
67600					; T2 = 0 (force level)
67700		JRST CLR2		; none left - exit
67800		; T1 = pageno
67900		; We put the page numbers in an array (ALIST) and clear
68000		; them several at a time.  This reduces the number of
68100		; PAGE. UUOs.
68200		; The zero'th element of ALIST contains the number of
68300		; pagenos in the list.
68400		AOS T2, ALIST		; increment arglist index, and put it in T2
68500		MOVE T3, T1		; T3 := pageno
68600		HRLI T3, 600000		; set bit0 - clear access perm
68700					;     bit1 - automatic access allow (6.03)
68800		MOVEM T3, ALIST(T2)	; put arg in list
68900		CAIL T2, ALSIZE-1	; is arglist full
69000		PUSHJ P, CLRALL		; yes - clear the pages we have so far
69100		AOJA T1, CLR1		; loop for next page (T1 is still pageno)
69200	
69300	CLR2:	; here when we are finished
69400		SKIPE ALIST		; unless arg list is empty
69500		PUSHJ P, CLRALL		; clear access for pages left in list
69600		POPJ P,			; return
69700	
69800	; end of subroutine CLRABS
69900	
70000	PAGE
70100	
70200	
70300	
70400	CLRALL:	; subroutine to clear the access bits for all the pages
70500		; in ALIST in one go
70600		; T1, T3 and T4 are not altered
70700		; T2 is overwritten
70800		; nonskip return
70900	
71000		TRACE 0,<Clear list>
71100	
71200		MOVE T2, [XWD .PAGAA, ALIST] ; set up call
71300		PAGE. T2,		; clear access bits
71400	CLERR:	ERROR<CLERR>		; oops!
71500		SETZM ALIST		; zero index
71600		POPJ P,			; return
71700	
71800	; end of subroutine CLRALL
71900	
72000	
72100	PAGE
72200	;*****************************************************************
72300	;
72400	; FINPAG
72500	;
72600	;*****************************************************************
72700	
72800	FINPAG:	; subroutine to find a candidate page
72900		; T1 = pageno to start looking at.  This must be a valid pageno
73000		; T2 = force level (see explanation below)
73100		; we consider all pages between T1 and TOPPAG as candidates
73200		; T1,T2,T3,T4 overwritten
73300		; skip return if candidate is found - T1 = candidate
73400		; nonskip return if no candidate found
73500	
73600	
73700		; The force level has the following effect
73800		; forcelevel = 0  - consider all incore pages as candidates
73900		; forcelevel = 1  - consider all incore pages which haven't been
74000		;			accessed in the last time slot
74100		; forcelevel = 2  - consider all incore pages which haven't been
74200		;			accessed in the last two time slots
74300		;
74400		; In any case we ignore pages which are not swapout possibilities
74500		;
74600		; page containing PC of fault
74700		; PFH page(s)   (if resident)
74800		; DDT pages if we are debugging the PFH
74900		; page containing APR trap word
75000		; page(s) containing Interrupt vector
75100		; page(s) containing PSI vectors
75200		; page(s) to which the pfh iis doing dump-mode I/O
75300		;
75400		; The caller must ensure that we don't look at page zero (i.e. T1 gt 0)
75500		; and that we don't look at page 777 for a VMX handler or a page in
75600		; a sharable hiseg (i.e. TOPPAG sensible).
75700	
75800		TRACE T1,<Find candidate>
75900	
76000		DMOVEM T1, FPSTP	; save start and force level values
76100		IDIVI T1, 44		; T1 := index into bitmap of start page
76200		MOVEM T1, FPINDX	; save it as well
76300		JRST FPAG2		; skip over beginning of loop
76400	
76500	FPAGL1:	; beginning of main loop
76600		; here to find next word in bitmap
76700		; T1,T2,T3,T4 unknown
76800		AOS T1, FPINDX		; increment index and put it into T1
76900	
77000	FPAG2:	; T1 = index into bitmap
77100		; T2,T3,T4 rubbish
77200		; find a bitword of candidates
77300		MOVE T3, INMAP(T1)	; T3 := INCORE
77400		SKIPLE T2, FPLEV	; if forcelevel gt 0
77500		ANDCM T3, UTTMAP(T1)	;    /\ NOT USED-THIS-TIME
77600		CAILE T2, 1		; if forcelevel gt 1
77700		ANDCM T3, ULTMAP(T1)	;    /\ NOT USED-LAST-TIME
77800	
77900	FPAGL2:	; beginning of inner loop
78000		; here to find next nonzero bit in bitword
78100		; T3 = bitword
78200		; T1,T2,T4, unknown
78300		JFFO T3, .+1		; T3 unchanged, T4 := bitno of nonzero bit
78400	
78500		; The order of the following instructions is important
78600		; If they are shuffled around the PFH may get into a loop.
78700	
78800		; calculate page number
78900		MOVE T1, FPINDX		; T1 := index into bitmap
79000		IMULI T1, 44		; T1:= pageno of bit 0 of candidate word
79100		ADDI T1, (T4)		; T1:= pageno corresponding to nonzero bit
79200	
79300		; T1 = page number
79400		; T3 = candidate bitword
79500		; T4 = bit number
79600		; T2 unknown
79700		CAMLE T1, TOPPAG	; pageno gt end?
79800		POPJ P, 		; yes - end of loop - error return
79900	
80000		JUMPE T3, FPAGL1	; if candidate word is empty get next
80100					; (This test must come after the
80200					; test for end of loop)
80300	
80400		; remove bit from candidate word
80500		MOVNI T4, (T4)		; T4 :=  -bitno
80600		MOVEI T2, 1		; T2 := bit35
80700		LSH T2, 43(T4)		; shift it left 35-bitno  places
80800		ANDCM T3, T2		; remove bit from candidate word
80900	
81000		CAMGE T1, FPSTP		; is pageno gt startpage
81100		JRST FPAGL2		; yes - go on to next candidate
81200		; (This test is necessary because
81300		; we start at the left hand end of the first word.
81400		; It must come after removal of the bit
81500		; from candidate word or we will loop forever.)
81600	
81700		; T1 = pageno
81800		;	1 le T1 le TOPPAG le 777
81900		; T3 = candidate bitword
82000		; T2,T4 rubbish
82100	
82200		; now check if this page is special in some way
82300	
82400		TRACE T1,<Candidate>
82500	
82600		MOVE T2, FPC		; PC at page fault
82700		HRLI T2, (T2)		; T2 = [address,,address]
82800		PUSHJ P, BADPGS		; check if it is this page
82900	
83000		; is this page part of the PFH?
83100		MOVE T2, .JBPFH		; T2 := [last address,,first address]
83200		PUSHJ P, BADPGS		; check if this page is part of pfh?
83300	
83400	IFN FTTEST<
83500		; if we are debugging the pfh then don't shove DDT pages out
83600		SKIPN T2, .JBDDT	; is DDT alive?   T2 := DDT addresses
83700		JRST FPAGT1		; no - continue
83800		PUSHJ P, BADPGS		; check
83900	> ; end IFN FTTEST
84000	
84100	FPAGT1:	; check for APR interrupt address
84200		SKIPN T2, .JBAPR	; APR trapping?  T2 := APR trap address
84300		JRST FPAGT2		; no - continue
84400		HRLI T2, (T2)		; T2 := [address,,address]
84500		PUSHJ P, BADPGS		; is this the APR page?
84600	
84700	FPAGT2:	; check for the interrupt vector
84800		SKIPN T2, .JBINT	; Interrupt handling?  T2 := interrupt vec adr.
84900		JRST FPAGT3		; no - continue
85000		HRLI T2, (T2)		; copy address into Left half
85100		ADD T2, [3,,0]		; lh := address of highest word in vector
85200		PUSHJ P, BADPGS		; is this an interrupt vector page?
85300	
85400	FPAGT3:	; check for PSI interrupt vectors
85500		SKIPN T2, PSIVEC	; user enabled for PSI interrupts?
85600		JRST FPAGT4		; no - continue
85700		; Format of T2 is
85800		; bits 0-8   index of highest PSI block
85900		; rh         address of PSI vector
86000		LSH T2, -31		; shift T2 25 places to the right
86100		; T2 is now bits 0-10 of the PSIVEC value
86200		; i.e. it is  (index of highest PSI block)*4   (maybe +something)
86300		;  = offset of highest PSI block  (they are 4 words long)
86400		ADDI T2, 3		; offset of last word of last block
86500		ADD T2, PSIVEC		; address of last PSI word
86600		HRLI T2, (T2)		; into left half
86700		HRR T2, PSIVEC		; starting address into right half
86800		PUSHJ P, BADPGS		; does this page contain PSI interrupt block?
86900	
87000	FPAGT4:	; Check whether this is a page to which the PFH
87100		; is trying to do dump-mode IO
87200		SKIPN T2, DMPADS		; doing dump-mode IO for user?
87300		JRST FPAGT5			; no - continue
87400		PUSHJ P, BADPGS			; check if this is in use
87500	
87600	FPAGT5:	; EUREKA!!!
87700		TRACE T1,<Candidate found>
87800		JRST SKPRET		; success return
87900					; T1 = pageno
88000	
88100	
88200	PAGE
88300	
88400	
88500	
88600	BADPGS:	; dirty subroutine to check whether a page contains all or part
88700		; of a vector.
88800		; T1 = pageno
88900		; T2 = [highest address of vector,,lowest address of vector]
89000		; T2,T4 are overwritten
89100		; T1,T3 preserved
89200		; if page contains all or part of vector we pop the return address
89300		; off the stack and jump direct to FPAGL2
89400		; nonskip return otherwise
89500	
89600		LDB T4, [POINT 9,T2,26]	; pageno of lowest address
89700		LSH T2, -33		; pageno of highest address
89800		CAIG T1, (T2)		; if T1 gt highest pageno
89900		CAIGE T1, (T4)		; or if T1 lt lowest pageno
90000		POPJ P,			;    then we are OK
90100		; no good - lowest page le T1 le highest
90200		POP P, T4		; remove return address from stack
90300		JRST FPAGL2		; and try next candidate
90400	
90500	; end of subroutine BADPGS
90600	
90700	; end of subroutine FINPAG
90800	
90900	
91000	PAGE
91100	;******************************************************************
91200	;
91300	; GETUUO
91400	;
91500	;******************************************************************
91600	
91700	GETUUO:	; Subroutine to get a UUO argument into core
91800		; PN = Page number
91900		;
92000		; The subroutine checks whether the user program has
92100		; got into a page fault loop trying to do dump-mode I/O
92200		; to more core than his physical allowance.
92300		; In this case the pfh simulates the I/O a page at a time.
92400	
92500		TRACE PN,<Fault on page for UUO>
92600	
92700		MOVE T1, FPC		; PC for this fault
92800		CAMN T1, LASTPC		; same as PC for previous fault ?
92900		JRST GUUO1		; yes
93000	
93100					; no - we are not in a page fault loop
93200		MOVEM T1, LASTPC	; remember this PC for next time
93300		MOVEI T1, 30
93400		MOVEM T1, LPCCT		; reset counter
93500		JRST GETIN		; get the page in
93600					; (and return direct to caller)
93700	
93800	GUUO1:	SOSLE T2, LPCCT		; have the last 24 faults all been from
93900					; the same PC?
94000		JRST GETIN		; no - get page (and return direct to caller)
94100		JUMPN T2, GUUO2		; yes - is this the 24'th fault?
94200		PUSHJ P, CHKIOD		; yes - is it a dump-mode I/O instruction?
94300		JRST SIMDMP		; yes - go and simulate I/O for user
94400					; (return direct to caller)
94500	GUUO2:	; no - the last 24 (or more) faults have all been from the same PC
94600		; but it isn't a dump-mode I/O instruction. (Or it is not
94700		; one which we feel confident to simulate.)
94800		; It is conceivable that he isn't actually in a loop
94900		; in which case we don't want to kill his program
95000		; so we let him continue until we are absolutely certain.
95100		CAMGE T2, [-140]	; have the last 100 page faults all been
95200					; from the same PC
95300		PUSHJ P, TOSMAL		; yes - tell him he is too small
95400					; if he continues he may have got more core
95500		JRST GETIN		; keep trying - get the page in
95600					; (and return direct to caller)
95700	
95800	; end of subroutine GETUUO
95900	
96000	
96100	PAGE
96200	;***************************************************************************
96300	;
96400	; CHKIOD
96500	;
96600	;***************************************************************************
96700	
96800	CHKIOD:	; Subroutine to check whether the user program is trying to
96900		; do a dump-mode I/O instruction
97000		; on entry T1 = PC
97100		; Nonskip return if the user is doing dump-mode i/o to a "nice" device
97200		;  with T2 = instruction
97300		; Skip return if not dump-mode I/O
97400		; In either case T1,T2,T3,T4 destroyed.
97500		;
97600		; The criterion for a "nice" device is that it can do
97700		; dump-mode I/O  1000 (octal) words at a time.
97800		; Devices allowed are disk, dectape
97900		; magtape is allowed for record dump i/o only
98000	
98100		PUSHJ P, GETINS		; get instruction word
98200					; T2 = instruction,  T3 = opcode
98300		CAIE T3, 56		; is it IN ?
98400		CAIN T3, 57		; or OUT ?
98500		JRST CHD1		; yes
98600		CAIE T3, 66		; is it INPUT ?
98700		CAIN T3, 67		; or OUTPUT ?
98800		JRST CHD1		; yes
98900		JRST SKPRET		; no - not an I/O instruction - skip return
99000	
99100	CHD1:	; The instruction is an I/O instruction
99200		LDB T4, [POINT 4,T2,12]	; T4 := channel number
99300		MOVE T3, [GETSTS 0, T1]	; inst we will XCT
99400		DPB T4,[POINT 4,T3,12]	; put chan no in XCT word for GETSTS
99500		XCT T3			; Do a GETSTS,   T1 := status
99600		ANDI T1, 17		; T1 := mode
99700		CAIGE T1, 16		; dump mode ? (16 or 17)
99800		JRST SKPRET		; no - skip return
99900		DEVCHR T4,		; get characteristics
     
00100		TLNE T4, 200100		; is this a "nice" device ?
00200					;   disc
00300					;   dectape
00400		POPJ P,			; yes - nonskip return
00500		TLNE T4, 000020		; is it a magtape ?
00600		CAIE T1, 16		; and is data mode record dump ?
00700		JRST SKPRET		; no - skip return
00800		POPJ P,			; yes - nonskip return
00900	
01000	; end of subroutine CHKIOD
01100	
01200	PAGE
01300	;*************************************************************************
01400	;
01500	; SIMDMP
01600	;
01700	;*************************************************************************
01800	
01900	SIMDMP:	; Subroutine to simulate dump-mode I/O for user
02000		; We split up his I/O command list, and do it 1 page at a time.
02100		; On entry  T2 = instruction
02200	
02300		TRACE 0,<Simulating Dump mode IO>
02400	
02500		LDB T1, [POINT 13,T2,12]	; get opcode and channel
02600		DPB T1, [POINT 13,MYIOIN,12]	; set up I/O instruction for XCT
02700		PUSHJ P, GETEA		; T1 := effective address
02800	
02900	SIMDL1:	; Beginning of main loop
03000		; T1 = address of command list entry
03100		PUSHJ P, GETWD		; T2 := command list entry
03200		JUMPE T2, SIMEND	; all finished ?
03300		JUMPL T2, SIMDL2	; is this an IOWD or a GOTO?
03400		MOVEI T1, (T2)		; it is a GOTO  T1 := address
03500		JRST SIMDL1		; loop
03600	
03700	SIMDL2:	; Beginning of inner loop
03800		; Here when T2 is a genuine IOWD
03900		; T2 = -LEN,,ADR
04000		DMOVEM T1, SIMSVR	; save T1,T2
04100		HLRE T1, T2		; T1 := -LEN
04200		CAMG T1, [-PAGSIZ]	; LEN gt 1000 ?
04300		MOVNI T1, PAGSIZ	; yes - transfer 1 page only
04400		HRLI T2, (T1)		; new -LEN into T2
04500		MOVEM T2, MYIOWD	; set up I/O instruction
04600	
04700		; now get the page(s) addressed by my IOWD into core
04800		SETCA T1,		; T1 := LEN-1
04900		ADDI T1, (T2)		; T1 := highest address referenced
05000		HRLM T1, DMPADS		; put it in LH of DMPADS
05100		HRRM T2, DMPADS		; lowest address in  RH
05200					; tells FINPAG not to shove these pages out
05300		PUSHJ P, GETWD		; make sure highest address page is in core
05400		HRR T1, DMPADS		; lowest address
05500		PUSHJ P, GETWD		; make sure page is in core
05600		DMOVE T1, SIMSVR	; restore T1=address, T2=IOWD
05700	
05800		; now do the partial IO
05900		XCT MYIOIN		; do it
06000		SKIPA			; nonskip return - continue
06100		JRST SIMDME		; skip return - error
06200		ADD T2, [PAGSIZ,,PAGSIZ]	; increment IOWD
06300		JUMPL T2, SIMDL2	; if there is still something to do
06400					; then loop
06500	
06600		; we are finished with this IOWD
06700		AOJA T1, SIMDL1		; go on to next word in command list
06800	
06900	
07000	SIMDME:	; here if we got an error return from the I/O instruction
07100		AOS  FPC		; pass on skip return to user prog
07200	
07300	SIMEND:	; here when we are finished simulating the I/O
07400		AOS T1, FPC		; return to user PC +1
07500		SETZM DMPADS		; tell FINPAG we are finished
07600		PUSHJ P, GETINS		; get the instruction
07700					; This ensures that we don't get a
07800					; pagefault when we try to return
07900					; to the user prog
08000		POPJ P,			; all done
08100	
08200	; end of subroutine SIMDMP
08300	
08400	
08500	PAGE
08600	;*******************************************************************
08700	;
08800	; GETWD
08900	;
09000	;*******************************************************************
09100	
09200	GETWD:	; Subroutine to get the contents of an address
09300		; It checks whether the address is one of the regs we
09400		; have saved, and ensures that the page is in core
09500		; on entry T1 = address
09600		; nonskip return with T1= address, T2 = contents
09700		; T3,T4 destroyed
09800	
09900		CAIL T1, T1		; is this one of the regs we have saved ?
10000		CAILE T1, P		;
10100		JRST GETWD1		; no - continue
10200	
10300		MOVE T2, SAVT1-1(T1)	; yes - get saved value
10400		POPJ P,			; and return
10500	
10600	GETWD1:	PUSH P, PN		; save PN
10700		PUSH P, T1		; save address
10800		LSH T1, -11		; pageno into T1
10900		MOVEI PN, (T1)		; and PN
11000		HRLI T1, .PAGCA		; set up call of PAGE. UUO
11100		PAGE. T1,		; get access bits for page
11200	GEWER:	ERROR<GEWER>		; oops!
11300		TLNE T1, 020000		; is it allocated but zero?
11400		JRST [	PUSHJ P, CREATE	; yes - create it in core
11500			JRST GETWD2 ]	; and allow access
11600		TLNE T1, 004000		; is it paged out?
11700		JRST [	PUSHJ P, GETIN	; yes - get it in
11800			JRST GETWD3 ]	; and carry on
11900		TLNN T1, 040000		; is access allowed?
12000	GETWD2:	PUSHJ P, ACCESS		; no - allow it
12100	
12200	GETWD3:	; here when the page is in core and accessible
12300		POP P, T1		; T1 := address
12400		MOVE T2, (T1)		; contents
12500		POP P, PN		; restore PN
12600		POPJ P,			; return
12700	
12800	; end of subroutine GETWRD
12900	
13000	
13100	PAGE
13200	;*********************************************************************
13300	;
13400	; GETEA
13500	;
13600	;*********************************************************************
13700	
13800	GETEA: ; subroutine to calculate the effective address of an instruction
13900		; on input T2 = inst, which can contain indirection bit and acc field
14000		; nonskip return
14100		; T1 = effective address
14200		; T2,T3,T4 smashed
14300	
14400		LDB T1, [POINT 4,T2,17]	; get acc field
14500		JUMPE T1, GETEA1	; unless it is zero
14600	
14700		PUSH P, T2		; save instruction
14800		PUSHJ P, GETWD		; T2 := contents of acc
14900		POP P, T1		; T1 := instruction
15000		ADDI T2, (T1)		; add address field to accumulator contents
15100		HLL T2, T1		; T2 is instruction with new address part
15200	
15300	GETEA1:	TLNN T2, 000020		; indirection bit set ?
15400		JRST GETEA2		; no - continue
15500		MOVEI T1, (T2)		; get adr
15600		PUSHJ P, GETWD		; T2 := contents
15700		JRST GETEA		; and loop
15800	
15900	GETEA2:	; all done - RH of T2 contains effective address
16000		MOVEI T1, (T2)		; T1 := EA
16100		POPJ P,			; return
16200	
16300	; end of subroutine GETEA
16400	
16500	PAGE
16600	;**************************************************************************
16700	;
16800	; GETINS
16900	;
17000	;**************************************************************************
17100	
17200	GETINS:	; Subroutine to get an instruction
17300		; It unwinds an XCT instruction
17400	
17500		; on entry T1 = PC
17600		; nonskip return
17700		; T2 = instruction
17800		; T3 = opcode
17900	
18000		PUSHJ P, GETWD		; T2 := instruction
18100		LDB T3, [POINT 9,T2,8]	; T3 :=  opcode
18200		CAIE T3, 256		; is it an XCT ?
18300		POPJ P,			; no - all done
18400		PUSHJ P, GETEA		; T1 := effective address
18500		JRST GETINS		; and loop
18600	
18700	; end of subroutine GETINS
18800	
18900	PAGE
19000	
19100	;******************************************************************
19200	;
19300	; GETBIT
19400	;
19500	;******************************************************************
19600	
19700	GETBIT:	; subroutine to calculate the index into the bitmap
19800		; and the bit corresponding to a page number.
19900		; page number in T1
20000		; on return T1 is index, T2 is bit
20100		; T3 overwritten, T4 preserved
20200		; nonskip return
20300	
20400		IDIVI T1, 44	; bit number in T2, index in T1
20500		MOVN T3, T2	; negative shift count into T3
20600		MOVEI T2, 1	; T2 := bit35
20700		LSH T2, 43(T3)	; shift bit left (35-bitnumber) places
20800		POPJ P,		; return
20900	
21000	; end of subroutine GETBIT
21100	
21200	
21300	PAGE
21400	;******************************************************************
21500	;
21600	; SKPRET
21700	;
21800	;******************************************************************
21900	
22000	SKPRET:	; JRST SKPRET is the same as doing a skip return
22100	
22200		AOS (P)		; set up skip return
22300		POPJ P,		; do it
22400	
22500	; end of SKPRET
22600	
22700	
22800	PAGE
22900	;*****************************************************************
23000	;
23100	; OUTNUM
23200	;
23300	;*****************************************************************
23400	
23500	IFN FTTEST<
23600	
23700	OUTNUM:	; subroutine used by the TRACE macro to print a number on the TTY
23800		; argument is in T1
23900		; it is printed in the form  "LH,,RH - "
24000		; nonskip return
24100		; no regs are altered (not even T1)
24200	
24300		PUSH P, T1		; save T1
24400		HLRZ T1, T1		; get left half of argument
24500		PUSHJ P, POCT		; print it
24600		OUTSTR [ASCIZ/,,/]	; make output pretty
24700		HRRZ T1, (P)		; get right half of argument from
24800					; where we stored it on the stack
24900		PUSHJ P, POCT		; print it
25000		OUTSTR [ASCIZ/ - /]	; more pretty-print
25100		POP P, T1		; restore T1
25200		POPJ P,			; return
25300	
25400	; end of subroutine OUTNUM
25500	
25600	
25700	POCT:	; subroutine to print an 18-bit octal number
25800		; argument is in T1
25900		; T1 is destroyed, but no other regs are altered
26000		; nonskip return
26100	
26200		PUSH P, T2		; save T2
26300		IDIVI T1, 10		; T1 := T1/8 ; T2 := T1 modulo 8
26400		SKIPE ,T1		; any leading digits
26500		PUSHJ P, POCT		; yes - print them recursively
26600		ADDI T2, "0"		; convert digit to ascii
26700		OUTCHR T2		; print it
26800		POP P, T2		; restore T2
26900		POPJ P,			; return
27000	
27100	; end of subroutine POCT
27200	
27300	
27400	> ;end of IFN FTTEST
27500	
27600	
27700	PAGE
27800	;****************************************************************
27900	;
28000	; DATA AREA
28100	;
28200	;****************************************************************
28300	
28400	SAVT1:	BLOCK 2		; register save area
28500	SAVT3:	BLOCK 2
28600	SAVPN:	BLOCK 2
28700	
28800	FORCFL:	BLOCK 1		; current forcing level
28900	SEMAFO: EXP 1		; semaphore
29000	LASTPC:	BLOCK 1		; PC of last page fault - to check for loops
29100	LPCCT:	BLOCK 1		; Number of times we have pagefaulted at the same PC
29200	DMPADS:	BLOCK 1		; points to pages involved in dump I/O
29300	
29400	FPSTP:	BLOCK 1		; private variables for FPAG
29500	FPLEV:	BLOCK 1		; FPLEV must come immediately after FPSTP
29600	FPINDX:	BLOCK 1
29700	
29800	MYIOIN:	IN 0, MYIOWD	; IO instruction we will XCT for dump-mode IO
29900	MYIOWD: BLOCK 1		; IO command list
30000		EXP 0		; end of IO command list
30100	
30200	SIMSVR:	BLOCK 2
30300	
30400	TOPPAG:	EXP 777		; highest possible candidate page no
30500	STKWD:	IOWD STKSIZ,STACK
30600	STACK:	BLOCK STKSIZ
30700	
30800	UTMBLK:	EXP MAPSIZE
30900	UTTMAP:	BLOCK MAPSIZE	; pages used in this time slice
31000	ULTMAP:	BLOCK MAPSIZE	; pages used in last time slice
31100	INMBLK:	EXP MAPSIZE
31200	INMAP:	BLOCK MAPSIZE	; pages currently in core (working set)
31300	
31400	ALIST:	BLOCK ALSIZE+1	; arglist for CLRABS
31500	RPFLS1:	EXP 1		; arglist used for removing old pfh
31600		EXP 400000000776
31700	RPFLS2:	EXP 1
31800		EXP 400000000777
31900	
32000	IFN FTTEST<
32100	ERMST:	ASCIZ/?PFHERR - PFH ERROR: /
32200	>
32300	PAGE
32400		LIT
32500	PAGE
32600		PFHEND=.+1
32700	
32800	> ; IFN P$VM
32900	
33000		END
33100	
33200