A mini-series on relocatable machine language begins in this month's column, plus a tip on a new product – an intelligent cable. Next month, the last part of the BAIT interpreter and more on relocatable machine language.
I have been working on a new project for COMPUTE! Books. By the time you read this, COMPUTE!'s Atari BASIC Sourcebook should be wending its way to your dealers' shelves and into your hands. Like Inside Atari DOS, the Sourcebook is a complete source listing of — what else? — Atari BASIC, along with a comprehensive explanation of how and why it all works.
Enough advertising. This month we will begin a mini-series on self-relocatable machine language. But before we begin all that, time out for some ruminations.
Machine Language Be Not Hard
Before we start investigating self-relocatable machine code on the 6502, I'd like to get up on my soapbox for a while and do a little preaching.
This month's sermon was inspired by a machine language program published in another magazine. The program seemed to me the epitome of poor programming techniques. And lest it seem that I am taking a cheap shot, let me hasten to add that the program works and works well. I am carping about the printed form of the program, not the results thereof.
In the tradition of any good preacher, then, let me give you some suggestions on how to write good, readable, maintainable machine language:
1. Always use plenty of comments (they cost nothing in the assembled code, unlike BASIC).
2. Never use absolute addresses (except in equates).
3. Never use absolute numeric constants (again, except in equates, though we might forgive an occasional constant 0 or 1).
4. Always use plenty of comments.
5. Always use long, meaningful names for labels. (Which makes more sense, ICCOM or IOCB.COMMAND ?).
6. Never branch to a location relative to the location counter (that is, never use "* + xx" or "*-xx").
7. Never use a comment that simply echoes the machine language code.
8. Always use plenty of comments.
9. Never change the location counter needlessly (that is, most programs should contain only one "* = ", except for the use of "* = * + xx" to reserve space).
10. If possible, always define a label before its first use.
11. Always thoroughly document the entry and exit values for a subroutine, taking special care to note what happens to the CPU registers.
12. Always use plenty of comments.
Those of you with some OSS software will see that I have taken a small pot shot at our own manuals in commandment 5. Well, I never said we were perfect. (Great, maybe, but not perfect.)
And those of you with Atari's Macro Assembler may object to using long labels since, even though AMAC allows long labels, it ignores all but the first six characters. Sorry, but I still think this rule should be followed. You just have to be more inventive to insure that labels are unique in the first six characters. (For example, IOCB. AUX1 and IOCB.AUX2 look the same to AMAC, so use IOCB.1AUX and IOCB.2AUX .)
Anyway, rather than go through each of those commandments one by one, let's look at an example subroutine coded with both worst and best techniques.
Example 1: Worst Technique
; EXAMPLE 1: print A register *= $1F00 LDX #11 STX $342 ; put 11 in location $342 LDX #0 STX $348 STX $349 JMP $E456 ;go to $E456
Example 2: Best Technique
; ; Example 2: Output the character in the A-register
; to file channel (IOCB) number zero (assumed to be the screen). ; ; ;; Entry: A-register contains the character ; Exit: Status of all registers unknown ; *= LOWMEMORY PRINTCHARACTER LDX #COMMAND.PUTBINARY STX IOCB.COMMAND; command for CIO LDX #0 ; use a zero buffer length STX IOCB.LOLENGTH ;tells CIO to output STX IOCB.HILENGTH ;contents of A register ; next line commented out...not needed since X already =0 ; LDX #0 ; specify IOCB zero JSR CIO ; let CIO do the real work ; Could check for errors here RTS ; all done
Enough said? I refuse to decipher programs like Example 1. Of course, Example 2 wouldn't be very useful either unless equates for the various labels were supplied (as in IOCB.COMMAND = $342), but at least most readers could understand its intent.
Regular readers will no doubt recall the many occasions on which I have ranted about staying out of Page 6 or about putting code at LOMEM or about writing code that is not specific to a particular hardware/software configuration. But, to be fair, sometimes it is hard to follow all of the rules, especially when adapting a program from a book or magazine.
Often, the real secret to writing adaptable code is in learning to write self-relocatable code. The techniques we will begin discussing this month are designed specifically for use with the 6502 microprocessor. While there will be several references to Atari internal structure, most of what is presented here is appropriate to Apple and Commodore machines as well.
And I will answer one more question before we start on the hard stuff: Why should we want to write self-relocatable code? Sorry, we don't have room for that answer this month. Wait until next month. (It's a good answer, honest!)
Actually, there is just one rule to remember in writing self-relocatable code: avoid references to absolute memory locations.
Unfortunately, this is often a very hard rule to follow. Fortunately, there are many places where we can make an exception to this rule.
For starters, look at the subroutine in Examples 1 and 2 above. Is it self-relocatable? Your first impulse might be to say no, since it references $342, $348, $349, and $E456, which are all absolute locations. And even if you do it right and use the equated labels of Example 2, they are still absolute, no matter what they look like.
But. Within the context of any given machine, there are always certain locations which never change. In particular, hardware locations, locations in ROM, and locations in the RAM (or values used and defined by ROM subroutines) cannot possibly change. An exception to this is when you plug in a new set of ROMs, and you can ask the software vendors about the fun and games the Atari 1200XL's new ROMs are giving them.
In the example given, $E456 (CIO) is in the Atari's OS ROM space. It is a guaranteed entry point to the OS command implementation code. It won't change (even in the new 1200, etc.).
And locations $340 through $34F (as well as $350 through $3BF) are in the IOCB space defined by Atari for use with CIO. Again, they won't and cannot change.
Finally, the command used (11) and the zero buffer length are values defined by the OS ROMs to have certain meanings. And if Atari changes these meanings, we are all in trouble, because Atari BASIC, PILOT, and more won't work then.
The result of all this? No matter where you assemble that example (that is, no matter where the "* = " places the code), the resultant machine object code will be precisely the same! Presto. That example is self-relocatable.
Surprisingly, a lot of the subroutines used with Atari BASIC follow the mold shown here: they simply set up some values in the Atari-specified memory locations and call an Atari-specified OS routine. They are implicitly self-relocatable.
So what is not relocatable? Generally, the prime culprits are:
1. References to RAM locations defined within the user's own code (for example, LDA, STA, INC, etc.).
2. Jumps (JMPs) to locations in the user's own code.
3. Calls (JSRs) to locations in the user's own code.
Let's make up an example just to illustrate potential problems.
* = $600 SAVEX * = * +1 MESSAGE .BYTE 'This is the message',0 ; ; this is the same code as the examples above ; PRINTCHARACTER LDX #COMMAND.PUTBINARY STX IOCB.COMMAND ; command for CIO LDX #0 ; use a zero buffer length STX IOCB.LOLENGTH ; tells CIO to output STX IOCB.HILENGTH ; contents of A register JMP CIO ; let CIO do the real work ; ; call here to print contents of 'MESSAGE' ; Entry conditions: none ; Exit conditions: none, no registers saved ;PRINTMESSAGE LDX #0 STX SAVEX ; initialize message pointer MSGLOOP LDX SAVEX ; get current message pointer LDA MESSAGE, X ; get next character of msg BEQ QUIT ; but quit if it's last char JSR PRINTCHARACTER ; else print it INC SAVEX ; point to next character JMP MSGLOOP ; and do another character ; QUIT RTS ;we are done
Do you see the problem areas? If we move this routine somewhere else in memory, the addresses of MESSAGE, PRINTCHARACTER, MSGLOOP, and SAVEX all change, and the object code associated with them changes also. This routine is definitely not self-relocatable.
But let's tackle each of the problem labels one at a time and see how we can change the references to each to make the code self-relocatable.
MSGLOOP is the easiest label to "fix." For example, if we change the line JMP MSGLOOP to BNE MSGLOOP, the label MSGLOOP is no longer a problem (since all branch instructions are always, by nature, self-relocatable).
And we could save the X-register on the stack (via TXA and PHA) and later retrieve and increment it similarly (via PLA, TAX, and INX), thus eliminating the need for SAVEX.
The PRINTCHARACTER routine could easily be eliminated in its entirety by placing its code inline in the middle of the PRINTMESSAGE routine. This is a good solution only if PRINTCHARACTER is not called by any other routine. It may also be an adequate solution if the routine being placed in-line is fairly small (as is PRINTCHARACTER) so that you can keep two or more copies around, if necessary.
But what do we do about MESSAGE, which is too big to put in a register? Or what would we do if PRINTCHARACTER was a long routine? And, most importantly, what do we do with a hunk of self-relocatable code once we have managed to produce it?
Next month we'll tackle those questions and others.
A Handy Product?
Do you do much work on both Apple II and Atari computers? If so, you could probably use a handy-dandy little device which we recently acquired.
Allen Prowell of Fresno, California, built us what amounts to an intelligent cable between our Apple and our Atari. It plugs into the joystick port on the Atari and into the game port on the Apple. It transfers ASCII files in either direction (doing "light conversion" on return characters, etc.). Very fast. It is much more convenient and reliable than using RS-232, and it moves over 1000 characters a second, including disk accesses.
As I said, this is a specialized product, but if you need it, call Allen (209) 227-4917. Using our C/65 and MAC/65 on both Atari and Apple, we have converted an 8K program in as little as two hours, including the transfers, assemblies, etc.
I think next month's column will be fairly long, what with the last part of BAIT and Part 2 of self-relocatable machine language. If I have room, though, I will introduce you to a new Atari graphics mode. Also, coming soon, information on some strange and wonderful new products for the Atari.