The World Inside a USR() Routine
Part 1 http://atariage.com/forums/blog/576/entry-13175-part-1-of-11-simple-assembly-for-atari-basic/
Part 2 http://atariage.com/forums/blog/576/entry-13176-part-2-of-11-simple-assembly-for-atari-basic/
Part 3 http://atariage.com/forums/blog/576/entry-13177-part-3-of-11-simple-assembly-for-atari-basic/
Part 4 http://atariage.com/forums/blog/576/entry-13178-part-4-of-11-simple-assembly-for-atari-basic/
Part 5 http://atariage.com/forums/blog/576/entry-13180-part-5-of-11-simple-assembly-for-atari-basic/
Part 6 http://atariage.com/forums/blog/576/entry-13181-part-6-of-11-simple-assembly-for-atari-basic/
Part 7 http://atariage.com/forums/blog/576/entry-13182-part-7-of-11-simple-assembly-for-atari-basic/
Part 8 http://atariage.com/forums/blog/576/entry-13183-part-8-of-11-simple-assembly-for-atari-basic/
Part 9 http://atariage.com/forums/blog/576/entry-13184-part-9-of-11-simple-assembly-for-atari-basic/
Part 10 http://atariage.com/forums/blog/576/entry-13185-part-10-of-11-simple-assembly-for-atari-basic/
Part 11 http://atariage.com/forums/blog/576/entry-13186-part-11-simple-assembly-for-atari-basic-the-end/
Atari BASIC's USR() function is powerful offering variable arguments and a return value. Many other BASICs' USR() or SYS() functions are relatively primitive taking no parameters and returning no results. This Atari-ism was a big help to me when learning C. The idea of subroutines called as functions with arguments and a return value was already introduced to me by Atari BASIC and USR().
Atari BASIC’s USR() function provides certain environmental conditions to a machine language routine. A machine language routine called via USR() requires good behavior conforming to the environment to insure safe execution and exit. A machine language routine meant to be called by USR() should follow these guidelines for acquiring arguments and returning to BASIC safely:
The 6502 A, X, and Y registers do not need to be saved before use.
The first byte on the stack provides the number of arguments passed to USR() excluding the first argument which is the starting address of the machine language routine. When there are no arguments this byte for the argument count will still be present on the top of the stack with the value 0. So, every machine language routine must always remove the argument count from the stack before the routine can exit safely.
Every argument is present on the stack as two-byte, 16-bit integers. So, directly passing Atari BASIC's six-byte, floating point values is not supported. BASIC converts floating point values into 16-bit integers (truncating the fractional part) before pushing them on the stack. Likewise BASIC strings cannot be passed, but the addresses of strings determined by ADR() can be passed.
Atari BASIC pushes the USR() arguments on the stack in order from right to left, so that the machine language routine will pop them off the stack in the same order as specified in the USR() statement.
Atari BASIC pushes each argument value on the stack in low byte, high byte order. This is the opposite of how the 6502 pushes an address on the stack. So, the values must be pulled off the stack in reverse – high byte pulled off first, low byte second. The routine must remove all arguments from the stack before it can safely exit.
The last item on the stack is the return address. The machine language program simply uses the RTS (Return from Subroutine) instruction to exit.
The environment is in Binary Coded Decimal (BCD) mode for decimal math by default. If Add or Subtract instructions will be used on binary values then the routine must clear the decimal mode (CLD instruction). Decimal mode does not have be re-enabled before the routine exits.
The machine language routine can return a result to Atari BASIC. The return value is a 16-bit integer stored at locations $D4/$D5 (or 212/213 decimal). In the USR() call X=USR(ADDRESS,...) BASIC converts the value at $D4/$D5 to an Atari floating point value and assigns it to the variable X.
If a program needs multiple return values or other kinds of output, then additional arguments to the USR() routine can provide addresses as targets for output and it is up to the machine language routine how to use those addresses to return other values.
A good machine language routine should be able to handle some adversity and protect against easy-to-manage, stupid-programmer tricks. For instance, a typo in a BASIC program could result in the program passing too many or too few parameters to USR(). This is an easy mistake to make, because BASIC can only verify the syntax of the USR() statement, not the number of parameters passed. A badly behaved routine would assume a specific number of parameters, and then cause the system to crash by returning with the stack in incorrect condition.
Also, if a routine is not expected to return a computed output value to BASIC, then it is good form to use the return value as a flag indicating successful completion or failure of the routine.
Next time, this assembly language discussion will include actual assembly language.
Many are the plans in the mind of a man, but it is the purpose of the Lord that will stand.