Advanced Topics

Pascal CSUBs

Implicit Compiler Global Space

The Pascal Compiler implicitly emits references to global variables exported from module SYSGLOBALS. These variables are used for error codes, TRY.. RECOVER management, and heap management. Global variables are referenced through offsets from 680x0 register A5 in the Pascal environment. The variables require a total of 22 bytes of space. Refer to the System Internals Document, "Pascal Listings," file INITLOAD.TEXT, module SYSGLOBALS for more information. These variables require the first 22 bytes of all SYSGLOBALS variables (INTEGER sysioresult is at offset -22).

The BASIC Operating System (OS) is written in MODCAL (a superset of the HP standard Pascal compilers; it is an internal HP tool). The Modcal compiler uses the same implicit global variables. While building the BASIC (bootable) system, an area in high RAM is reserved for these globals (and others); it can be referred to as BASIC Globals. The BASIC operating system uses the first 22 bytes of this area for the implicit compiler globals (TRY..RECOVER, etc). When running BASIC, register A5 points to the base of this area.

If there are no explicitly declared global variables in a CSUB, this BASIC Globals area will also be used for any implicit compiler globals needed by the CSUB. BUILDC will not issue a prompt for a global COM name.

When a CSUB is called (from BASIC), execution control is eventually passed to CSUBENTRY. CSUBENTRY (with no explicit globals declared) leaves A5 pointing to the base of the BASIC Globals area. CSUBENTRY, after setting up the stack correctly, will then transfer control to the actual user code.

Explicit CSUB Module Global Space

If the CSUB requires additional globals, the BASIC Globals space does not have room for the CSUB globals in addition to the 22 bytes required for implicit SYSGLOBALS. It is necessary to use COM for the entire global space. The size of the COM area (in bytes) is equal to the 22 bytes of implicit globals (the SYSGLOBALS area) plus the CSUB globals. BUILDC adds the 22 bytes whenever it detects explicit global space.

The 22 bytes of global space are actually reserved by module CSUBENTRY. This is the first module linked in (by streaming GENC.TEXT) on the first link pass. When modifying GENC to add/remove modules, CSUBENTRY must remain as the first module linked. The Librarian will offset the CSUB globals down 22 bytes to account for this. If the link order is changed CSUB globals will occupy the first 22 bytes. However, the CSUB utilities will always locate the implicit compiler globals to these first 22 bytes; thus a space conflict will be encountered. Remember, CSUBENTRY must be the first module linked in the GENC.TEXT stream file!

Now when CSUBENTRY is called, it sets A5 to the base of the appropriate COM area before transferring to user code. If this is not done correctly (i.e., because modules were not linked before running BUILDC), assignment to CSUB globals will overwrite BASIC system variables.

Trapping Errors

The Pascal ESCAPE statement may be used to do error reporting from a CSUB.


ESCAPE(some_number);

This statement causes one of three things to happen.

The following table shows the possible error numbers and how they are interpreted by the system:

-890...-881 -880...-32 -31...-1 0 1...32767
Error Numbers
Pascal Errors BASIC Definition
-32767...-891 SYSTEM ERROR
reserved by operating system
SYSTEM ERROR
ERROR (escape_code = 400)
Pascal exit, no error
ERROR (escape_code MOD 1000)
Another error recovery technique is to use Pascal TRY-RECOVER sequence. TRY-RECOVER is documented in the Pascal Workstation System manual. Conversely, ESCAPEing with positive value can be trapped/reported by BASIC using ON ERROR and ERRN.

Assembly Language CSUBs

CSUBs may be written in assembly language either as Pascal-type modules or as stand-alone routines. Pascal calling sequences for passing parameters must be observed.

Information about writing assembly language routines can be found in the Pascal Workstation System manual. "The Assembler" chapter, particularly "The Programming System" section, should be read before attempting to write a routine.

Assembly language routines are limited in the same way as Pascal routines. They must be relocatable at link time.

A Simple Routine

This module performs a palindrome check on a BASIC string. A palindrome is a string that says the same thing forward and backwards (e.g., "radar").

The BASIC declaration is:


CSUB Palindrome(Test$, INTEGER Result)

The result will be "1" if Test$ is a palindrome, and "0" otherwise. This is a simple algorithm and therefore ignorant of case, punctuation, and spaces.

The main algorithm sets up an address register pointing to each end of the string. It then compares the current characters working its way to the center of the string. This is repeated until:

Case 1 indicates the string is not a palindrome; Case 2 indicates the string is a palindrome.

For this assembly routine, give BUILDC a null name (just press [Enter]) when asked for a module name.



*         Define some handy mnemonics

front           equ     a0      Pointer to the front half of the string
back            equ     a1      Pointer to the back half of the string
result          equ     a2      Pointer to a 16 bit INTEGER
return          equ     a3      The return address

temp            equ     d0      Used for calculations and comparisons

*       Define the entry point

        def     palindrome

*       Go for it.  First unload the stack

palindrome equ *
        movea.l (sp)=,return
        movea.l (sp)=,result
        movea.l (sp)=,front
        addq.l  #4,sp           The dimentryptr is not used in this CSUB

*       Get the current string length and use this to set up
*       the front and back pointers.  Remember, a BASIC string is
*       a 16-bit integer followed by a packed array of characters.

        clr.l   temp
        move.w  (front),temp            Get string length
        addq.l  #2,front                The first character is always here
        lea     0(front,temp),back      Back points to last character=1

*       Use the pre-increment and post-decrement of the address registers
*       to compare characters and update pointers.

loop    move.b  -(back),temp            Get next "back" character;
        cmp.b   (front)=,temp           compare it to the "front";
        bne.s   false                   quit if there is a mismatch

        cmpa    front,back              If "back" is still greater...
        bgt     loop                    ...keep trying

true    move.w  #1,(result)             Set it true
        jmp     (return)

false   clr.w   (result)
        jmp     (return)

        end

File Access Library Topic

Normally, when you attempt to purge a file, BASIC checks to see if it is open before allowing the PURGE. Also, files are automatically closed when the @Name (I/O path name) assigned to it is going to be de-allocated or reused.

These actions are implemented by a search through the BASIC symbol tables for @Names. Obviously, this search cannot find the memory used for a file control block by a CSUB using the FAL. The way to get around this is to use a file control block which is the value area for an I/O path. There are two ways to do this. First, you can pass the I/O path as a parameter to the CSUB (see the "Writing CSUBs" chapter in this manual for details). Second, you can create a COM which includes an I/O path, then access it by using FIND_COM, and use the value area corresponding to the @Name as the file control block. Be careful to direct the fcb_ptr to the correct memory location so that fields in the file control block will be aligned with the correct fields in the @Name.

Value areas occur in the common block in the reverse order of the declaration of the variable name in BASIC. Thus, FIND_COM returns the address of the last variable in the declaration of the common block, the next-to-last variable lies just above it in memory, etc. The first variable in the common declaration occupies the highest addresses in the value area for the common block.

This technique will enable FAL_PURGE to check if a file has been FAL_OPENed, which would otherwise not happen.

NOTE
If you use this technique, do not zero the fcb before using it. The BASIC system will take care of any necessary initializations. If the fcb has been used as an I/O path in BASIC, zeroing it in the CSUB may cause incorrect behavior.

Some special considerations may occur when using an I/O path as an idtable (file control block). Both FAL_CLOSE and FAL_OPEN will close an I/O path which is already open, including I/O paths for files, devices, and buffers. Attempting to use any other FAL routines with an idtable which is closed or which is open (as an I/O path) on a device or buffer will give an error 51 (file not assigned). While using an I/O path as a file control block (idtable) provides some additional protection in the areas of automatic file closure and improper file PURGEs, it is recommended that the program explicitly close files when done using them and before purging them.