; ****************** BUCKET FILE BLOCK I/O ROUTINES **************************
;
;
; Subroutines to read in and write out a Bucket File.  The PME_OPEN subroutine
; reads in the input bucket file and zeroes all the counts therein (if zeroing
; is requested) and the PME_CLOSE subroutine creates and writes out a new bucket
; file.  The PME_INAME and PME_ONAME subroutines can optionally be called to
; specify the file names of the input and output files--if they are not called,
; a default file name is supplied instead.  These routines are documented in
; more detail below.  Bucket files are read and written with RMS Block I/O.
;
;
;		Written by Bert Beander, March, 1979.



	.TITLE	PMEBLKIO Performance Measurement and Evaluation
	.IDENT	/V01-01/

	.LIBRARY "PMEDEFS.MLB"		; Default macro library

	PMEDEFS				; Define all Bucket File tags
	$FABDEF				; Define all FAB$ tags
	$RABDEF				; Define all RAB$ tags
	$DSCDEF				; Define all DSC$ tags


; Globally defined data items--the bucket file buffer base address, the group
; record base address, and the default counter address.
;
	.PSECT	PME_DATA,NOEXE,LONG
PME_BASE::	.LONG	0		; Holds address of the start of the in-
					;      memory copy of the bucket file
		.LONG	0		; Required by $EXPREG system service
PME_GRPADDR::	.LONG	0		; Address of first Bucket Group Record
PME_DEFCNTR::	.LONG	0		; Address of default counter for
					;      out-of-range PC values


; FABs, RABs, and other data areas
;
DEFNAME:	PMEDEFNAME		; Pure copy of the default file name
DEFSIZE		=	.-DEFNAME	; Size of default file name
SENTINEL:	.ASCII	"*<PME$>*"	; Sentinel at start of bucket file

	.ALIGN	LONG			; Longword align the FABs and RABs
INFAB:	$FAB	FNA=INFNAME,FNS=DEFSIZE,DNM=<.PME>,FAC=<BIO,GET>
INRAB:	$RAB	FAB=INFAB,BKT=1,ROP=BIO
OUTFAB:	$FAB	FNA=OUTFNAME,FNS=DEFSIZE,DNM=<.PME>,FAC=<BIO,PUT>,MRS=512
OUTRAB:	$RAB	FAB=OUTFAB,BKT=1,ROP=BIO
INFNAME:	PMEDEFNAME		; File name of input bucket file
		.BLKB	MAXNAMSIZ-DEFSIZE ;
OUTFNAME:	PMEDEFNAME		; File name of output bucket file
		.BLKB	MAXNAMSIZ-DEFSIZE ;
HDRBUF:	.BLKB	HDRRECSIZ		; Buffer to hold Bucket File Header
NAMSIZ:	.WORD	0			; Temporary for holding file name size


; Macro to branch to a specified location if R0 contains an error status
;
	.MACRO	BRERR	BRADDR,?LOCAL	;
	BLBS	R0,LOCAL		; Branch around BRW if status is good
	BRW	BRADDR			; On bad status, go to BRADDR
LOCAL:					; Generated local label
	.ENDM	BRERR			;


	.PSECT	PME_CODE,EXE,NOWRT,LONG,PIC


; ************* PME_OPEN:  READ IN AN EXISTING BUCKET FILE ********************
;
;
; Subroutine to read in a bucket file.  It can be called in two ways:
;
;			CALL PME_OPEN
;
;			CALL PME_OPEN(BUFFER,MAXSIZ,CLRFLG)
;
; This causes the input bucket file (whose name was specified by default or
; by a previous call on PME_IFILE) to be opened, checked for validity, read
; into memory, and closed.  A buffer for the file contents is created dynam-
; ically unless the BUFFER parameter is specified.  (If the bucket file size
; exceeds MAXSIZ, the first longword in BUFFER is zeroed and PME_OPEN returns
; without reading in the bucket file.)  All bucket counts in the file are then
; cleared to zeroes if the CLRFLG parameter is .TRUE. and the HDRFLG_CLR bit in
; the file Header Record is set.  If either CLRFLG or HDRFLG_CLR is not set, the
; bucket counts are left untouched.  If no parameters are passed to PME_OPEN,
; the CLRFLG parameter is assumed to be .TRUE..  Certain global data items are
; also primed for use by the PME_COUNT and PME_COUNTER routines.
;
; Note: MAXSIZ is the size of BUFFER in longwords.
;
;
; Open the bucket file and connect the RAB to the FAB
;
	.ENTRY	PME_OPEN,^M<R2>		; Read Bucket File entry point

	$OPEN	FAB=INFAB		; Open the input bucket file
	BRERR	INERROR			; Check for error
	$CONNECT  RAB=INRAB		; Connect the RAB to the FAB
	BRERR	INERROR			;


; Read in the Bucket File Header Record and check it for validity
;
	MOVAL	HDRBUF,INRAB+RAB$L_UBF	; Set the buffer address in the RAB
	MOVW	#HDRRECSIZ,INRAB+RAB$W_USZ ; Set the buffer length
	$READ	RAB=INRAB		; Read the Bucket File Header Record
	BRERR	INERROR			;
	CMPL	HDRBUF+HDRSENT,SENTINEL	; Check the sentinel in the first
	BNEQ	INERROR2		;      quadword to make sure this
	CMPL	HDRBUF+HDRSENT+4,SENTINEL+4 ;  is a valid bucket file
	BNEQ	INERROR2		;
	MOVL	HDRBUF+HDRNXTPTR,R0	; R0 = the total file size in bytes
	CMPL	R0,#HDRRECSIZ		; Make sure the file size is in the
	BLEQ	INERROR2		;      valid range--else error out
	CMPL	R0,#65535		;
	BGTR	INERROR2		;
	MOVW	R0,INRAB+RAB$W_USZ	; Set the file size in the RAB

	BRB	AROUND1			; Branch around the branch to the
INERROR2: BRW	INERROR3		;      BRW--this prevents branch
AROUND1:				;      byte displacement overflows


; See if the caller is supplying the bucket file buffer.  If so, save its
; address in PME_BASE and check out its size.
;
	TSTB	(AP)			; Did the user pass any parameters?
	BEQL	GETMEM			; If not, we get our own memory
	MOVL	4(AP),PME_BASE		; He did--get the address of his buffer
	MULL3	@8(AP),#4,R0		; Compute his buffer size in bytes
	CMPL	R0,HDRBUF+HDRNXTPTR	; Is it big enough for this bucket file?
	BGEQ	READ			; If yes, go to read in the file
	CLRL	@PME_BASE		; No--zero first longword of his buffer
	CLRL	PME_BASE		; Forget his buffer's address
	RET				; Return--no more can be done


; Get enough memory for the whole bucket file from $EXPREG and save the address
; of that space in PME_BASE.
;
GETMEM:	ADDL2	#511,R0			; R0 = the number of 512-byte pages
	DIVL2	#512,R0			;      needed to hold the bucket file
	$EXPREG_S  PAGCNT=R0,RETADR=PME_BASE  ; Get the necessary memory
	BRERR	INERROR			; Make sure it worked


; Now read the whole bucket file into the memory we just got.  After that,
; close the bucket file.
;
READ:	MOVL	PME_BASE,INRAB+RAB$L_UBF; Fill address of the buffer into RAB
	$READ	RAB=INRAB		; Read in the whole bucket file
	BRERR	INERROR			;
	$CLOSE	FAB=INFAB		; Close the bucket file
	BRERR	INERROR			;


; Loop through the Bucket Group Records to set ABSTBLPTR (the absolute address
; of the corresponding index table) in each such record.
;
	MOVL	PME_BASE,R2		; R2 = the file header base address
	ADDL3	HDRBGRPTR(R2),R2,R0	; R0 = addr of first Bucket Group Record
10$:	ADDL3	PME_BASE,RELTBLPTR(R0),ABSTBLPTR(R0)	; Set group's ABSTBLPTR
	ADDL2	#GRPRECSIZ,R0		; Go to next Bucket Group Record
	TSTL	BUCKETSIZE(R0)		; If this is a real Bucket Group Record,
	BNEQ	10$			;      loop to initialize it


; If the HDRFLG_CLR bit is set and the CLRFLG parameter allows it, clear all
; bucket counts to zeroes.
;
	TSTB	(AP)			; Is there a CLRFLG parameter?
	BEQL	20$			; If not, we are allowed to clear counts
	BLBC	@12(AP),40$		; CLRFLG is there--go to 40$ if .FALSE.
20$:	BITL	#HDRFLG_CLR,HDRFLAGS(R2); Is zeroing of counts requested?
	BEQL	40$			; If not, branch around to 40$
	CLRL	HDRDEFCNTR(R2)		; Clear the default counter
	CLRL	HDRNUMRUNS(R2)		; Clear the number of runs contributing
					;      counts to this bucket file
	ADDL3	HDRNAMPTR(R2),R2,R1	; R1 = pointer to end of Bucket Records
	ADDL3	HDRBRECPTR(R2),R2,R0	; R0 = pointer to start of Bucket Records
30$:	CMPL	R0,R1			; Are we at end of Bucket Records yet?
	BGEQ	40$			; If yes, go to 40$
	CLRL	COUNT(R0)		; Clear the count in this Bucket Record
	ADDL2	#BKTRECSIZ,R0		; Go to next Bucket Record
	BRB	30$			;


; Set the address of the bucket file header's default counter in the
; global PME_DEFCNTR and set the address of the first Bucket Group Record
; in the global PME_GRPADDR.  (These globals are used by the PME_COUNT
; and PME_COUNTER subroutines.)  Then return to the caller.
;
40$:	MOVAB	HDRDEFCNTR(R2),PME_DEFCNTR	; Set default counter's address
	ADDL3	HDRBGRPTR(R2),R2,PME_GRPADDR	; Set up Group Record pointer
	RET					; Return


; Handle error conditions encountered in PME_INIT.
;
INERROR:
	$EXIT_S	CODE=R0			; Just stop right here--this gets the
					;      status code in R0 printed out
INERROR3:
	BPT				; Bad Bucket File Header--just stop
	$EXIT				; We should never get here



; ************* PME_CLOSE:  CREATE AND WRITE A NEW BUCKET FILE ***************
;
;
; Subroutine to write out a new bucket file.  It can be called two ways:
;
;			CALL PME_CLOSE
;
;			CALL PME_CLOSE(BUFFER)
;
; This causes a new bucket file, whose name is given by default or by a
; previous call to PME_ONAME, to be created.  If BUFFER is not specified,
; the contents of the buffer created by or passed to PME_OPEN is then
; written out to the new bucket file and the file is closed.  If BUFFER
; is specified, the contents of that buffer, which is assumed to have the
; standard bucket file format, is written out instead.
;
;
; See if the caller is passing the BUFFER parameter.  If so, pick up the
; address of that buffer and store it in PME_BASE.
;
	.ENTRY	PME_CLOSE,^M<R2>	; Write Bucket File entry point

	TSTB	(AP)			; Did the user pass any parameters?
	BEQL	15$			; If not, go to 15$
	MOVL	4(AP),PME_BASE		; But if so, pick up the buffer address
15$:					;      and stuff it into PME_BASE


; Fix up the Bucket File Header Record--fill in the sentinel value and
; increment the number of runs which have tallied PC values in it.
;
	MOVL	PME_BASE,R2		; R2 = base address of buffer
	MOVQ	SENTINEL,HDRSENT(R2)	; Fill the magic sentinel value into
					;      the Bucket File Header
	INCL	HDRNUMRUNS(R2)		; Increment the number of runs which
					;      have added counts to this file


; Set the buffer address and size in the RAB and set the allocation size in
; the FAB.  Also make sure the buffer size is in the valid range.
;
	MOVL	HDRNXTPTR(R2),R0	; R0 = the buffer size in bytes
	CMPL	R0,#HDRRECSIZ		; Make sure this buffer size is
	BLEQ	OUTERROR2		;      within the valid range
	CMPL	R0,#65535		;
	BGTR	OUTERROR2		;
	MOVL	R2,OUTRAB+RAB$L_RBF	; Set buffer address in the RAB
	MOVW	R0,OUTRAB+RAB$W_RSZ	; Set buffer size in the RAB
	ADDL2	#511,R0			; Compute the number of 512-byte blocks
	DIVL3	#512,R0,OUTFAB+FAB$L_ALQ;      to allocate and store in the FAB

	BRB	AROUND2			; Branch around BRW instruction--this
OUTERROR2: BRW	OUTERROR3		;      prevents branch byte displace-
AROUND2:				;      ment overflows


; Now create the new bucket file, connect the RAB to the FAB, write out the
; contents of the bucket file, and close the file.  Then return to the caller.
;
	$CREATE	FAB=OUTFAB		; Create the new bucket file
	BRERR	OUTERROR		; Make sure it worked
	$CONNECT  RAB=OUTRAB		; Connect the RAB to the FAB
	BRERR	OUTERROR		;
	$WRITE	RAB=OUTRAB		; Write out the bucket file contents
	BRERR	OUTERROR		;
	$CLOSE	FAB=OUTFAB		; Close the new bucket file
	BRERR	OUTERROR		;
	RET				; All done--now return


; Handle output error conditions.
;
OUTERROR:
	$EXIT_S	CODE=R0			; Just stop right here--this gets the
					;      status code in R0 printed out
OUTERROR3:
	BPT				; Bad bucket file length--just stop
	$EXIT				; We should never get here


; **************** PME_INAME AND PME_ONAME: SET FILE NAME ********************
;
;
; These two entry points allow the user to specify the file names to be used
; for the input and output bucket files.  These routines are called as follows:
;
;			CALL PME_INAME(INFILENAME)
;
;			CALL PME_ONAME(OUTFILENAME)
;
; where INFILENAME and OUTFILENAME are character strings, passed by descriptor,
; containing the desired file names.  These routines simply copy the passed
; file name to a local buffer and strip off any trailing blanks.  No other
; checking is done on the file name strings.  If the passed string is completely
; empty (all blanks) or if these routines are not called in the first place, the
; file name "PMEFILE" is supplied by default.
;
;
; Subroutine to specify the name of the input bucket file.
;
	.ENTRY	PME_INAME,^M<R2,R3,R4,R5,R6,R7> ; Set Input File Name entry point

	MOVAB	INFNAME,R6		; R6 = address of local fname buffer
	MOVAB	INFAB,R7		; R7 = address of input FAB
	BRB	GETSTR			; Go to process name string


; Subroutine to specify the name of the output bucket file.
;
	.ENTRY	PME_ONAME,^M<R2,R3,R4,R5,R6,R7> ; Set Output File Name entry point

	MOVAB	OUTFNAME,R6		; R6 = address of local fname buffer
	MOVAB	OUTFAB,R7		; R7 = address of output FAB


; Copy the passed file name string to our local file name buffer
;
GETSTR:	MOVL	4(AP),R1		; R1 = address of string descriptor
	MOVW	DSC$W_LENGTH(R1),R0	; R0 = the string length
	CMPW	R0,#MAXNAMSIZ		; If the length exceeds the size of our
	BLEQ	10$			;      local buffer, truncate it
	MOVW	#MAXNAMSIZ,R0		;
10$:	MOVW	R0,NAMSIZ		; Save the name size
	MOVC3	R0,@DSC$A_POINTER(R1),(R6) ; Copy the file name to local buffer
					   ;   (this clobbers registers R0-R5)


; Now scan the name string backwards to strip off any trailing blanks
;
	MOVW	NAMSIZ,R0		; R0 = the file name length again
	ADDL3	R0,R6,R1		; R1 = pointer to the end of the string
20$:	CMPB	-(R1),#^A" "		; Scan backwards for the first non-blank
	BNEQ	30$			;      or the start of the string
	SOBGTR	R0,20$			;
30$:	MOVB	R0,FAB$B_FNS(R7)	; Set the file name size in the FAB


; If the string turned out to be empty (zero length), fill in our default
; file name (PMEFILE) instead.  Then return to the caller.
;
	TSTW	R0			; Is the string empty?
	BGTR	40$			; If not, go to 40$ and return
	MOVC3	#DEFSIZE,DEFNAME,(R6)	; But if so, fill in the default file
	MOVB	#DEFSIZE,FAB$B_FNS(R7)	;      name and its size
40$:	RET				; Return

	.END
