ASSEMBLER PROGRAMMING TECHNIQUES FOR THE ATARI ST ================================================= by Peter Hibbs. PASSING IN-LINE PARAMETERS TO SUB-ROUTINES ========================================== In common with all micro-processors, the ATARIs 68000 processor uses sub-routines for sections of code which may be repeated more than once within a program and it is often necessary to pass data to a sub- routine from the calling program. There are several methods of passing data to a sub-routine. The simplest method is to load the required values into data or address registers and then call the sub-routine which then fetches the data from these registers. This system is useful where the data values are variable and sufficient registers are available but is not ideal where a large amount of data (such as a string of text) is to be transferred. Another method (which is used extensively by the ATARI GEM and BIOS) is to push the data values (or the address of a string of data) onto the stack and then the sub-routine has to recover the data from the stack. This method is fairly flexible but is somewhat untidy especially where several strings of data may need to be passed to the routine. A third method (which does not seem to be used much in ATARI software) is to pass the data as in-line data embedded in the calling program immediately after the sub-routine call. This method is only suitable for fixed data, however, since its value is determined at assembly time. This system has the advantage though that the data concerned is easily visible at the point in the program where it is used and not in some look-up table somewhere else in the source code. Also the values (such as the string data or addresses) can be easily altered during program development. This article describes this technique with an example of a sub-routine which use in-line data. With the powerful instruction set of the 68000 processor the task of accessing the data in the calling program from the sub-routine is quite simple. For example, consider the following code where the data string 'text string',0 is to be passed to the sub- routine PRINT which then sends it to the printer port. bsr print call sub-routine dc.b 'text string',0 data to be passd even The calling program calls the required sub-routine and immediately follows it with the byte constants of the required string and terminated with a NUL (0) character. Note that since the string consists of byte size data it could end on an odd address which would cause the processor to generate an 'address' error on return to the calling program. The 'even' pseudo-instruction pads out the string with a NUL character so that the next instruction after the call always starts at an even address. In the sub-routine the code would look something like this :- print movem.l a0/d0,-(sp) save any registers move.l 8(sp),a0 fetch string address sub-routine code execute sub-routine move.l a0,8(sp) a0=end of string+1 movem.l (sp)+,a0/d0 restore registers rts return When a sub-routine is called, the stack pointer is first decremented and then the current return address is pushed onto the stack (4 bytes) with the stack pointer pointing at the last byte saved. On entry to the routine any registers which may be corrupted by the routine are first pushed onto the stack (as required). The next instruction (using the 'address register indirect with displacement' addressing mode) copies the return address from the stack into register a0. The displacement value required in this case is 8 since the previous instruction saved 8 bytes (i.e. two longwords) onto the stack. Address register a0 will now be pointing at the first byte of the data string in the calling program code, the sub-routine fetches the data and processes it as required. When all the data has been processed, address register a0 must be pointing at the next word after the data string in the calling program code (i.e. the next main program instruction). At the end of the sub-routine the address in address register a0 is copied back into the stack using the same displacement instruction so that the return instruction will return program execution to the correct point in the calling program. The saved registers are restored and the RTS instruction returns control to the calling program. The following sub-routine demonstrates a practical example of this technique. It is often necessary in programs to load a memory buffer with a text string such as a filename or message. This example copies a string of data bytes into a specified memory buffer. The calling program specifies the address of the buffer (as one longword) followed by the data string which is terminated with a NUL value. Since all data strings in the ATARI operating system are usually terminated with a NUL value, this character is also copied into the buffer as well as acting as a terminator. The format for the call from the main program is :- bsr copy_string call sub-routine dc.l buffer address of buffer dc.b 'text string',0 string data+NUL even ensure even address . . buffer ds.b 20 destination buffer The operation of the sub-routine is fairly self-explanatory, address register a0 is used as a pointer to the data string, register a1 is used as a pointer to the memory buffer and register d0 holds the data between each fetch and store instruction. One problem which arises when byte size data is being passed to a sub- routine is that the data may end on an odd address. Since the processor can only fetch instructions from even addresses, the return address must be incremented to the next even address if it finishes on an odd address but not changed if it finishes on an even address. When the data transfer has been completed bit 0 of the address register a0 is tested to see if it is a 1 (odd address) or 0 (even address). Unfortunately, the 68000 instruction set does not include a bit test instruction which allows this to be done on an address register so the a0 register is first copied into the data register d0. The least significant bit of d0 is tested and if it is a 0 the routine is terminated after first restoring the saved registers. If the bit is a 1 (an odd address) the a0 register is incremented by one and the routine terminated as before. The 'even' pseudo-instruction in the calling program ensures that the processor fetches the next instruction from the correct address. COPY_STRING SUB-ROUTINE. ;ENTRY destination address defined as in-line data. ; data string defined as in-line data. ;EXIT string data copied into defined buffer ; no registers changed. copy_string movem.l a0-a1/d0,-(sp) save regs (12 bytes) move.l 12(sp),a0 fetch return address move.l (a0)+,a1 fetch destination addr copy_string1 move.b (a0)+,(a1)+ copy byte to buffer bne copy_string1 was it 0 ?, no repeat move.l a0,d0 copy a0 to d0 btst #0,d0 is address even ? beq copy_string2 branch if yes addq.l #1,a0 inc address if not copy_string2 move.l a0,12(sp) copy ret addr to stack movem.l (sp)+,a0-a1/d0 restore regs rts return The above routine was written with the DEVPAC 2 editor/assembler program from HiSoft. There is one point that the programmer should note however. When using the Monitor program to debug the main program, the in-line data following the sub-routine will confuse the debugger program when single stepping with the CTRL T option (executing a sub- routine without stepping through it). When single stepping through a program the CTRL Z option should be used and the sub-routine stepped through otherwise the debugger program will try and execute the in-line data as instructions.