Oh boy! What we all have been waiting for... XCALLS! Yes
beginners, this month we will take a look at how to write a BASIC
XCALL subroutine. Since you have been following the series with
extreme dedication, the following presentation should be fairly
straight forward. Only a few new "tricks of the trade" will be
disclosed, and the subroutine examined is an easy XCALL to
understand. Enough of the introductions and on with the session.
Before We Begin
Before you can dash madly off and create all those XCALLS you
have been dreaming about, you need to know how AMOS exchanges
variable information back and forth between a subroutine and a BASIC
program. Quickly you grab your Assembly Language Library
documentation and begin scanning for the info, right? WRONG! Why (I
wish I knew the answer) does Alpha Micro contain the variable passing
documentation in the back of the AlphaBASIC manual??? It's a mystery,
folks. Therefore, it might be of some use to whip out your AlphaBASIC
Manual if you need more details than the ones I give you in this
month's submission - see chapter 18.
Usually, an XCALL is created to have AMOS perform a duty from
within BASIC that is difficult (if not impossible) to do with only
the AlphaBASIC instructions. And, usually, the XCALL requires you to
"pass" some variables into the subroutine. The XCALL then uses these
variables while executing the assembled instructions of the
subroutine, and finally AMOS returns these variables (possible
changed) back to the BASIC program. That's basically the "behind the
scenes" actions of an XCALL.
In order to "pass" the desired variables back and forth, there is
a pre-defined "template" that AMOS expects to decipher. An example
BASIC XCALL line and the corresponding template are presented below:
The format of an XCALL:
{{ BASIC program}}
|
|
XCALL {{subroutine name}},{{var 1}},{{var 2}}, {{...}}
|
|
{{ End of BASIC program}}
The expected template for AMOS:
Register A0 : Indexes the "impure" variable area. Think of A0
as the .OFINI pointer you have learned to use.
Register A3 : Points to the base of the Argument list. Contained
in this list is all the pertainent information
about the variables being passed (discussed below).
Register A4 : Points to the base of the free memory area that may
be used by the external subroutine as workspace.
This is actually the pointer to the first word PAST
the argument list.
Register A5 : This is the pointer to an arithmetic stack, similar
to your user stack (register A7 or the SP).
Therefore, A5 MUST NOT BE USED BY THE SUBROUTINE!
General Specifications of the Template
The number of arguments (or variables) that you may pass in is
theoretically unlimited, but for most XCALLS, this number will not be
too large. When a variable is "passed", it is passed by REFERENCE.
This is the fancy-pants way of saying that the VALUES of the
variables are not passed into the subroutine, but the ADDRESSES of
each variable is what is actually "passed".
Again, register A3 points to the base of the argument list.
Contents of this list are as follows:
* Word one (first two bytes) contains the binary count of the number
of variables actually passed.
* After this first word, there is a detailed "breakdown" for EACH
variable. This "breakdown" is a 10-byte description:
+ 1 Word containing the variable type code (only bits 0-2 are used).
binary value 000 : unformatted
binary value 010 : string
binary value 100 : floating point
binary value 110 : binary
+ 1 Longword containing the absolute address of the variable
(remember, the variable's address has actually been "passed")
+ 1 Longword containing the size of the variable in bytes.
In graphical form, the argument list looks something like this:
XCALL USERNO
The following XCALL is a great example for the beginner learning
how to create XCALLS. It is called USERNO - "User Number". What this
small XCALL will do is to return your particular "JOB" number, i.e.,
if you are listed third whenever you do a SYSTAT, then USERNO will
return to your BASIC program the value "3". The following is the
listing for USERNO:
; this program searchs the job table for the user's job and returns the line
; number in the table of the user's job. syntax for calling from basic is
; XCALL USERNO,FFFF
; where FFFF is a floating point number.
; written by Robert Kurz, 1547 Cherrywood Drive, Martinez, CA 94553
; donated by Robert Kurz & Doug Shaker
;
; Slightly modified for AAA 04-23-86
;
SEARCH SYS ;Collecting monitor calls
SEARCH SYSSYM
OBJNAM USERNO.SBR ;notice .SBR extension
USERNO: PHDR -1,0,PH$REE!PH$REU ; Re-entrant and re-usable
CMPW (A3)+,#1 ; number of arguments must be 1
BNE SYNTAX ; else print out syntax error msg.
MOVW (A3)+,D5 ; type of argument
ANDW #7,D5 ; mask off "type" (bits 0..2)
CMPW D5,#4 ; must be floating point
BNE SYNTAX ; else syntax error message
MOV (A3)+,A2 ; address of variable to A2
JOBIDX A1 ; get the address of our job
MOV JOBTBL,A0 ; get the address of the jobtable
CLR D1 ; and clear the "count"
LOOP: INC D1 ; increment job number
CMP A1,(A0)+ ; compare this entry to our job
BNE LOOP ; if not the same try the next one
FLTOF D1,@A2 ; convert to floating point
RTN ; all done
SYNTAX: TYPECR <XCALL USERNO must have one floating point argument>
RTN
END
Line-by-Line, Byte-by-Byte
SEARCH SYS
SEARCH SYSSYM
These two lines should look VERY familiar. What the SEARCH does
is grab all of the monitor calls located in the above two .UNV files
on DSK0:[7,7]. We have discussed this portion of the code thoroughly.
OBJNAM USERNO.SBR
Although OBJNAM is another "thoroughly" discussed topic, notice
that USERNO is informing AMOS that what it is to create, once
assembled, is USERNO.SBR - not the default value of assembly,
USERNO.LIT! Now you can see why I stressed placing the expected
assembled file name even on a .LIT creation: consistent formating.
USERNO: PHDR -1,0,PH$REE!PH$REU
USERNO: is a label (not really necessary, but it acts as a
"header" for the source code making it easier to read). Then the
usual Program HeaDeR. Again, if a file is to be LOADed into system
memory, AMOS will check to make sure that the program is re-entrant
and re-useable by consulting the header. This is a good programming
practice I have always stressed.
CMPW (A3)+,#1
CoMPare the Word pointed to by A3 and see if whatever A3 points
to is a "1", and at the same time, point A3 to the first byte PAST
the word we are looking at. Remember, the () means "look at what the
register is pointing to", and the "+" means "increment the register
by the appropriate amount" - due to the Compare WORD, this amount is
2 bytes. Since the first word is the count of the variables used by
USERNO, what this line is effectively doing is checking to make sure
that one and only one variable was passed.
BNE SYNTAX
Branch if Not Equal to the label SYNTAX. Upon every Compare, the
next instruction is usually a "poll" to the status register checking
to see the results of the Compare. If the above Compare did NOT
produce an "equal" result, then the next instruction would be at the
line SYNTAX.
MOVW (A3)+,D5
MOVe the Word pointed to by register A3 into the data register D5
and at the same time increment the pointer A3 by the appropriate
amount. Since A3 was previously incremented a few instructions ago,
A3 is now pointing to the first byte of the "variable type". This
instruction has the effect of moving the variable type word into the
register D5 where some future work will take place. A3 is then
incremented to point to the first byte of the longword that contains
the address of the variable that was passed in to USERNO (see the
diagram above).
ANDW #7,D5
"AND" the Word inside register D5 with the value of 7. What AND
does is "copy" the BITS inside a register, the bits copied depends on
the "#" value ANDed. AND will copy only the bits that are "on" from
the "#" (7 in this program). Bits that are "off" from the number
causes AND to copy zeros.
15..............0 <-- bit location indicators
| |
V V
0000000000000100 <-- register D5's contents (hopefully)
AND 0000000000000111 <-- bit representation of #7
-------------------------
0000000000000100 <-- AND copies the bits at location
0..2 if "#" is a 7.
D5 now contains this value.
The AND was performed to isolate the bits that contain the variable
type definition. D5 now contains the type definition bits.
CMPW D5,#4
CoMPare the Word in register A2 with the value of four. This is
to check to make sure that the variable type passed into the
subroutine was indeed a floating point variable. Any value other than
a four would cause problems.
BNE SYNTAX
Again, the status register was set according to the CoMPare Word
above. The status register should have the zero flag SET if the
comparison was equal. If the comparison was NOT equal, then Branch to
the label SYNTAX.
MOV (A3)+,A2
Move the value pointed to by A3 into the register A2, and then
increment A3 by the appropriate amount. The value moved into A2 is
the address of the floating point variable that was passed into the
subroutine. The incrementation on register A3 is +4 (due to the MOVe
for MOVe longword). A3 now points to the size of the variable (not
used in this subroutine).
JOBIDX A1
The monitor call JOBIDX will return the base address value of
YOUR JOBs JCB. Remember, each JOB on the system has a Job Control
Block that AMOSL.MON maintains to monitor your JOBs every move.
Register A1 now contains the pointer to the beginning of this block
(an actual location inside system memory).
MOV JOBTBL,A0
We have seen this MOVe often. What this MOVe does is move into
the register A0 a pointer that points to the base of the JCB address
table (see the January .LOG submission). We will be scanning the JCB
pointer table looking for a match between the table pointer and your
pointer stored in register A1, counting as we go.
CLR D1
D1 will be used as a "counter". Each time we scan the JBC table
and it doesn't match the base address of YOUR JBC pointer, the
counter will be incremented. CLR makes sure that no bits are set
inside the register.
LOOP: INC D1
LOOP is a label that the subroutine will jump back to each time a
match is not found between JCB addresses. It is here where we
INCrement the counting register before each JCB to JCB comparison.
CMP A1,(A0)+
CpMPare the longword that is contained in A1 (YOUR JCB) to the
longword pointed to by A0 (the sequential JCB addresses contained in
the JOBTaBLe), and then increment A0 by the appropriate amount (+4).
Again, we are looking for a match between JCB addresses. A0 will
sequentially scan down the list of JCBs looking at one JCB for each
time through the LOOP.
BNE LOOP
If there is no match, Branch if Not Equal to the label LOOP.
FLTOF D1,@A2
We have found a match, so convert the longword contained in D1
(the count) to a two's complement floating point number (to match the
variable type passed in), and place this number into the memory
location pointed to by register A2. This just so happens to be the
memory location of the floating point variable we passed in to
USERNO (from the instruction above MOV (A3)+,A2).
RTN
ReTurN to BASIC upon finding the JOBs user number.
SYNTAX: TYPECR <XCALL USERNO must have one floating point argument>
SYNTAX: contains the "error message" that the subroutine will use
whenever incorrect usage of USERNO is called. All the instructions
after the BNE SYNTAX up to this line are bypassed if the branch takes
place.
RTN
ReTurN to BASIC after typing out the error message.
END
End of the source code.
Wrapping Up
This subroutine only used one variable. If more variables are
used, the same pattern as the one above would be employed, but you
have to keep track of the bytes pointed to by A3 and make sure you
are changing values in the addresses of the variables and not making
changes to the variable type bytes etc.
Examine other subroutines found on the Network [100,52], and then
try to create your own simple subroutine and see if you can make it
work. Hope this series was of some help, and call me if you have
questions. Bye!