On entry to a machine code extension (ie not an EXEC'd job or a CALLed routine) certain registers are set up with very useful values. These are :
Table 7.2. Register Settings On Entry To SuperBasic Extensions
Register | Value |
---|---|
A1 | Allegedly points to the top of the maths stack relative to A6, however, see below. |
A3 | Points to the start of the name table entry for the first parameter. |
A5 | Points to the first byte AFTER the name table entry for the last parameter. |
A6 | Base address of SuperBasic. WarningDo not change this register. |
A1 is supposed to point at the top of the maths stack (see below) relative to A6, but I have found out the hard way that this is only the case when the procedure or function being executed has some parameters and they have been fetched. A1 is set to the amount of space used (or free) on the maths stack on entry to a procedure. (See Maths Stack below for full details.)
A3 points at the address of the first byte of the first entry in the name table for this procedure or function. Again, this is relative to A6.
A5 points at the address of the first byte AFTER the last name table entry for this procedure or function. Again this is relative to A6.
A6 should never be changed as it points to the base of the SuperBasic job and almost all the various routines involving the maths stack and getting/returning parameters rely on addresses being relative to A6.
So we can now check to see how many parameters we have by the following calculation :
(A5 - A3) / 8
There are 8 bytes in each name table entry. Full details of the name table entries are given below.
If we have 3 parameters, then the name table entries will look like the following :
+----------------------------+
(A5,A6)----->| Random garbage .... | <--- Highest address
|----------------------------|
| Third parameter's 8 bytes |
|----------------------------|
| Second parameter's 8 bytes |
|----------------------------|
(A3,A6)--+-->| First parameter's 8 bytes | +--- Lowest address
| +----------------------------+ |
| |
+-----------------------------------+
So (A3,A6) points to the first byte of the first parameter and is the lowest address, (A5,A6) points to the first byte past the last parameter and is the highest address.
The first name table entry starts at 0(A3,A6) and ends at 7(A3,A6). The second starts at 8(A3,A6) and ends at 15(A3,A6) and the last starts at 16(A3,A6) and stops at 23(A3,A6).
When fetching parameters from the name list onto the maths stack, we can use some vectored utilities to get them for us. These allow the retrieval of strings, long words, integers (short words) and floating point values. They all expect A3 and A5 to be set up correctly as above. A3 and A5 are trashed by the routines, so if you have to check any parameter separators etc, then you must do it before calling the fetch routines.
When the routines return, they set D3.W to the number of parameters fetched and set A1 to the correct value for the top of the maths stack - relative to A6 of course. Now we can access the values of each parameter separately as we like. On return the first parameter in the list is stored at 0(A1,A6), the next is above the first and so on. When fetching parameters p1, p2, p3 from a procedure or function call, they will end up on the maths stack in the correct order - 0(A1,A6) will be pointing at p1 on the stack.
The parameter fetching routines are :
Table 7.3. Vectored Routines For Parameter Fetching
Vector | Purpose |
---|---|
CA_GTINT | fetch integer parameters (2 bytes each). |
CA_GTLIN | fetch long parameters (4 bytes each). |
CA_GTFP | fetch floating point parameters (6 bytes each). |
CA_GTSTR | fetch string parameters (variable length). |
They require to be called as follows :
start move.w ca_gtint,a2 ; Fetch all params as integer words jsr (a2) ; Do it tst.l d0 ; Did it work? beq.s ok ; Yes rts ; Must return to SuperBasic with errors ok ... ; carry on here
At this point, D3.W can be tested to check that the correct number of parameters has been fetched.
start cmpi.w #4,d3 ; Were there 4 parameters? beq.s ok_4 ; Yes moveq #-15,d0 ; Bad parameter error code = -15 rts ; and back to SuperBasic ok_4 ... ; Carry on here
To access the parameters we need to get the data off of the maths stack and into our working registers, as follows :
move.w 0(a6,a1.l),d1 ; Parameter one move.w 2(a6,a1.l),d2 ; Parameter two move.w 4(a6,a1.l),d3 ; Parameter three move.w 6(a6,a1.l),d4 ; Parameter four
and so on. Now that we have our parameters, we need do nothing more with the maths stack if we are inside the code of a procedure. If we are in a function then we MUST tidy the maths stack. This is simply done by adding the size of all parameters on the stack to A1. In our example we have 4 word length parameters, so we should add 8 to A1 as follows :
adda.l #8,a1 ; Reset maths stack
As mentioned, there is no need to do this in a procedure, but if you have to learn to do it for a function, you are as well to learn to do it for everything - that way you don't forget to do it and cause a hanging QL.
Tidying a stack with strings on is more difficult and it is probably best done as each one is removed. For example, say we have two strings on the stack after a call to CA_GTSTR then we get them off as follows :
cmpi.w #2,d3 ; Were there two strings? beq.s ok ; Yes moveq #-15,d0 ; Bad parameter rts ; Exit to SuperBasic ok lea buffer_a,a2 ; Destination for one string lea 0(a6,a1.l),a3 ; Source for string bsr copy_str ; Copy move.w 0(a6,a1.l),d0 ; Size word addq.w #3,d0 ; Make bigger bclr #0,d0 ; Make even add.w d0,a1 ; Add D0 to A1 - sign extends remember !
Ok, so we added the size of the first string plus 2 for the size of the size word as well, to A1 having made it even so the stack is now cleared of the first string. This leaves one string with its size word sitting at 0(A6,a1.l) ready for the next copy :
lea buffer_b,a2 ; Destination for next string lea 0(a6,a1.l),a3 ; Source for string bsr copy_str ; Do the copy move.w 0(a6,a1.l),d0 ; Size word addq.w #3,d0 ; Make bigger bclr #0,d0 ; Make even add.w d0,a1 ; Add D0 to A1 - sign extends remember !
and there you have a tidy stack once again.
You could ask 'if we have to restore A1 to its value on entry, why not just save A1 and then restore it afterward?'. Like this :
start move.l bv_rip(a6),a1 ; Fetch top of Maths Stack move.l a1,-(a7) ; Stack it for later ; Do lots of stuff here - fetching parameters etc move.l (a7)+,a1 ; Restore A1 ; and so on
Well, you could, but at certain times there will be a hung QL and you will not know why. The reason is simple, but difficult to find or trace. When you fetch parameters onto the maths stack, it can MOVE IN MEMORY. Preserving the original value is fine if the stack stays put, but if it moves and you set BV_RIP to the old value, you can get into all sorts of trouble. It is best to keep the stack tidy using the methods described above.
You may well also ask "What is all this add 3 and clear bit 0 nonsense then?" Think about it in binary for a bit. We have the word size of the string in D0.W and we must ensure that we add an even number of bytes to A1. We must also remember to add 2 to A1 for the size of the size word itself.
Lets try this with an even number first of all. Even numbers are detected by bit zero being clear, so :
+----------------------+
| D0 | D0 + 3 | Result |
|----------------------|
| 2 | 5 | 4 |
| 4 | 7 | 6 |
| 10 | 13 | 12 |
+----------------------+
So you can see what is happening. D0 always ends up being D0 + 2 and is always even. This is good as it is what we want. What about odd numbers then?
+----------------------+
| D0 | D0 + 3 | Result |
|----------------------|
| 3 | 6 | 6 |
| 5 | 8 | 8 |
| 11 | 14 | 14 |
+----------------------+
So is this good then? Remember that the maths stack must be kept even. When odd length strings are copied onto it by CA_GTSTR it pads out the space on the stack with a rubbish byte (CHR$(0) to be precise) which is never used. The size remains word remains odd.
So for an odd sized string we need to add 2 for the size word, the odd number of bytes and one spare for the padding. Our 3 lines of code handle this for all cases - even or odd sized strings. The code is good !
Of course it would be simple to do this :
move.w 0(a6,a1.l),d0 ; Size word btst #0,d0 ; Is it even? beq.s even ; Yes addq.w #1,d0 ; Add 1 for padding byte for odd sized strings even addq.w #2,d0 ; Add 2 ro the size word add.w d0,a1 ; Add D0 to A1 - sign extends remember !
But this is extra typing and takes longer, so the simple case shown above, works all the time.
What do you do if you want to get hold of two long words and a string?
Let us assume that you are writing a procedure that has this format :
DO_SOMETHING long_1, long_2, string_1
This has two different types of parameters and we cannot fetch them all in one go unless we can read the long parameters as strings and convert them ourselves. It is quite easy to fetch these parameters - you just do it in two goes.
In the code we know that A3 and A5 hold the start and stop addresses of the parameters in the Name Table. If we set A5 to be A3 + 16 and then collect long words we will get our two long words. We can then set A5 back to its original value and set A3 to this less 8 and fetch the final parameter as a string. Here we go then :
get_longs move.l a5,-(a7) ; Save last parameter pointer lea 16(a3),a5 ; A5 now thinks thare are only two parameters move.w ca_gtlint,a2 ; Fetch all parameters as longs jsr (a2) ; Do it tst.l d0 ; OK? beq.s got_long ; Yes rts ; Exit with error code got_long cmpi.w #2,d3 ; Were there two? bne.s bad_params ; No, bale out move.l (a7)+,a5 ; A5 holds address of final string lea -8(a5),a3 ; Pretend we have only one parameter move.w ca_gtstr,a2 ; Fetch as strings now jsr (a2) ; Do it tst.l d0 ; OK? beq.s got_string ; Yes rts ; Exit with error code bad_params moveq #-15,d0 ; Bad parameter error rts ; Exit to SuperBasic got_string ; continue from here
Ok, so now what does the maths stack look like? Remember when fetching parameters they end up on the stack in the order you want them with the first at the lowest address and the next above it and so on. This time, we fetched two longs and a string in two different calls. This means that after the first fetch the maths stack is :
+--------+
| Long_2 |
|--------|
(A6,A1)---->| Long_1 |
+--------+
But then we fetched a string and it got put onto the maths stack so it now looks like this :
+--------+
| Long_2 |
|--------|
| Long_1 |
|--------|
| bytes |
| bytes |
|--------|
(A6,A1)---->| size |
+--------+
QDOS is very helpful here. If during the course of fetching the string, the maths stack had to be moved in memory, QDOS will preserve the current contents so that 'long_1' and 'long_2' will still be there when you come around to using their values. Nice !
In this discussion we mentioned the name table. This is discussed in detail next. Do you get the feeling that this article is written upside down?