Classic Computer Magazine Archive COMPUTE! ISSUE 46 / MARCH 1984 / PAGE 160

COMMODORE

Floating Subroutines

Louis F. Sander

Here is a subroutine that lets you automatically combine BASIC and machine language. It's easy, flexible, and inventive. For all VIC, 64, and all PETs except Original ROM models.

It's often desirable to include one or more machine language (ML) subroutines in your Commodore BASIC program, especially when the program must be optimized for speed. There are several ways of combining the BASIC and ML, each having its own advantages and disadvantages. The method described here puts your ML in a protected area at the end of the BASIC program, where it will automatically SAVE and LOAD along with the BASIC. Other ways of doing the same thing have one huge disadvantage—after the ML is in place, the BASIC program cannot be changed in any way, ever. This method overcomes that drawback, letting you make any number of subsequent changes to the BASIC program.

Our new technique requires your ML to be completely relocatable. That is, it requires that your ML will work properly at any place in memory, so long as the proper entry point is used. In some cases this restriction will keep you from using the new technique, but this may not happen often. Many, if not most, useful ML subroutines are completely relocatable, or can be made so.

Reserving Space

As a BASIC program runs, the operating system keeps track of certain important addresses by storing them in zero page locations called pointers. One of these is the Start Of Variables (SOV) pointer, which normally holds an address one byte higher than the end of whatever BASIC program is in memory. If that program changes size, the SOV pointer keeps track of its end +1, so the computer knows where to store its variables without writing over the program. By altering the SOV pointer to make it point artificially high in memory, we can reserve space for ML between the end of BASIC and the newly redefined Start Of Variables.

When we put our ML program into the reserved space, it is effectively made a part of our BASIC program, and there are several accompanying benefits. Since it's part of the BASIC program, the computer will never overwrite it unless told to. Since it lies above the end of program marker (three zeros at the very end of a BASIC program), the computer won't try to relink it when BASIC lines are changed. And when the BASIC program is SAVEd, the ML will go right along with it, because the computer automatically saves everything from the Start Of BASIC to the Start Of Variables.

The trouble comes when we change the BASIC program—as the real BASIC program's end moves up or down in memory, our ML moves with it. If our ML program is completely relocatable, it runs the same in any part of memory, so moving it doesn't matter, as far as proper execution goes. What does matter is that our ML's entry point is then no longer known, so we can't tell what number to put in our SYS statement.

If we could find the first byte of the relocated ML, we could adjust our SYS statement accordingly, and everything would be fine. Fortunately, BASIC has a pointer which makes the ML easy to find; the pointer in question always holds the address of the first byte in whatever BASIC line is currently being executed. If our BASIC program's final line adds its own length to the address in that pointer, and stores the result in a variable, the variable holds the address of the first byte of our ML. Once we execute this line, say as a subroutine, the BASIC program knows where the ML is, and can easily make the proper SYS calls.

Setting It Up

To use the new technique, you add the ML finder line as the last line in your main BASIC program, then change the SOV pointer so it points above the highest byte you want to reserve for ML. Finally, you execute a CLR (not CLEAR SCREEN, the other one), which corrects some other pointers.

A short BASIC subroutine can make these things automatic and foolproof. You append it to your main BASIC program, RUN it, then delete most of its lines. If your library includes an APPEND program, the automation is easy; if you lack APPENDing capability, doing things manually may be easier. The accompanying programs are the subroutine I use, in versions for all Commodore machines except Original ROM PETs. The comments below apply to all versions:

Important Addresses
64 VIC Upgrade & 4.0 VIC PET/CBM
Start Of Variables Pointer 45-46 45-46 42-43
Current Lint-Pointer 61-62 61-62 58-59
USR Vector 785-786 1-2 1-2

Line 63991 checks the accuracy of the all-important line 63999, which is the line that finds our machine language.

Lines 63992, 63995, and 63996 move the SOV pointer, which requires the temporary use of two memory locations. The ones used here are the USR vector locations, but you can use others if you'd like.

Line 63997 is a decimal-to-hex converter.

Line 63999 sets variable ML equal to the address of the first byte of the reserved ML area. The line must be entered exactly as listed, with no embedded spaces, and must be the last line in your BASIC program. (That's why it has the highest line number allowed in BASIC.)

Here are step-by-step instructions for entering your automation subroutine and checking its accuracy:

  1. Type the appropriate subroutine into your computer.
  2. SAVE the subroutine onto tape or disk.
  3. RUN it and observe the screen. If you get an error message, you've made a mistake in typing line 63999. Reload what you SAVEd, correct your error, then go to step 2.
  4. If there is no error message, enter a 6 in response to the # BYTES prompt. You'll get some screen messages and a READY prompt.
  5. At this point, there should be six bytes reserved for ML, just above the end of your BASIC program. Your screen should show the addresses of the lowest and highest bytes in the reserved area. Immediately below the ML area should be the three zeros which mark the end of BASIC; immediately above it should be four bytes of 218 decimal, which were put there as a marker by the ZZ% business in line 63996. If you know how to examine memory, you should check that the zeros and 218's are where they whould be, for proof that your subroutine is working correctly. (If you use a monitor to examine memory, the hex version of the 218's will announce the good news in dramatic fashion. Try it.) If the zeros and 218's aren't in the right places, something is wrong; check your work, find the errors, and start again from step 2.
  6. Now put something into those six bytes and SAVE the subroutine. Turn your computer off to destroy what is in memory, then LOAD what you just saved. Check to make sure your six bytes of ML traveled along with the BASIC. If they did, you're finished.

Using It

The subroutine you SAVEd in step 2 has now been proven to work perfectly. The one you saved in step 6 is OK too, but it has some ML appended to it. When you want to add some machine language to the end of a BASIC program, just put the step 2 subroutine at the end of the BASIC program, in one of these ways:

  1. LOAD the BASIC program, then use an APPEND routine to add the subroutine, or
  2. LOAD the subroutine, then type in the BASIC program, or
  3. LOAD and LIST the subroutine, then LOAD the BASIC; add the subroutine to it by putting your cursor on each of the previously LISTed subroutine lines and hitting RETURN. The VIC's screen is too small for this; all others are fine, but you must be careful with your cursor, or important subroutine lines will scroll off the screen as the BASIC loads. When LOADing the main program from the Datassette, put your cursor on the first letter of the READY prompt, type LOAD [space] [space], press PLAY, then hit RETURN. Doing otherwise may cause too much scrolling. When using a disk, put your cursor on the first letter of the READY prompt, then enter your LOAD command in the normal way.

Once the subroutine is in place, do a RUN 63991 and follow the instructions on the screen. You can reserve any number of bytes for ML, up to the limit of your memory. The subroutine shows the current boundaries of the ML area, and you should put your ML there immediately, since the boundaries will move if you change the BASIC program. Caution: When you delete lines, you must do it line by line from the keyboard; Toolkit or other programming aids' deletes will detach your ML from the end of BASIC.

You can now make all sorts of changes to the BASIC program, and your ML subroutine will follow its end up and down like a shadow. You can even delete every line of BASIC; in that case, a SAVE will save your ML as though it were a BASIC program itself. And if you ever want to expand an ML area already in use, you can just reappend the subroutine and run it again; it will tack more reserved area onto that you already have!

To use the ML from the BASIC program, have an early line do a GOSUB 63999, which will put the address of the first ML byte into variable ML. Use this information to find the machine language entry point, then call the ML program at will. If the entry point is the first byte of the ML, SYS ML will do the job; otherwise, use SYS ML + X, where X is the offset of the entry point from the first byte.

So there's the ideal technique for combining BASIC and relocatable ML—it's easy to set up, easy to use, and has no undesirable restrictions. Once you SAVE a fully tested subroutine to automate the setup process, it becomes a fine-tuned tool that you can use with ease for many years.

Program 1:

Combining BASIC And ML On The 64

63990 REM COMMODORE 64 VERSION
63991 GOSUB63999 : IFPEEK(ML-1) + PEEK(ML-2) +
      PEEK(ML-3)THENPRINT"63999 IS BAD" :	E
      ND
63992 INPUT"{CLR}# BYTES TO RESERVE FOR M
      L";A : J = 256 : B = PEEK(45) + J * PEEK(46) : C =
      A + B
63993 PRINT"{DOWN}NOW PUT THE ML INTO : " : P
      RINT"{DOWN}DECIMAL"B"-"C-1 : PRINT"
      {DOWN}{4 SPACES}HEX ";
63994 K = 4096 : H = B : GOSUB63997 : PRINT" - "; : H
      = C : GOSUB63997 : PRINT
63995 PRINT"{DOWN}THEN DELETE LINES 63991
      -63997.{DOWN}" : D = INT(C/J) : POKE786,D
63996 POKE785, C-J * D : POKE45,PEEK(785) : POKE
      46,PEEK(786) : CLR : ZZ% = -9510 : END
63997 H = H/K : FORI = 1TO4 : H% = H : H$ = CHR$(48 + H%-
     (H%>9) * 7) : PRINTH$; : H = 16 * (H-H%) : NEXT
63998 REM * 63999 FINDS ML START ADDR
63999 ML = PEEK(61) + 256 * PEEK(62) + 31 : RETURN

Program 2:

Combining BASIC And ML On The VIC

63990 REM VIC-20 VERSION
63991 GOSUB63999 : IFPEEK(ML-1) + PEEK(ML-2) +
      PEEK(ML-3)THENPRINT"63999 IS BAD" : E
      ND
63992 INPUT"{CLR}# BYTES FOR ML";A : J = 256 :
      B = PEEK(45) + J * PEEK(46) : C = A + B
63993 PRINT"{DOWN}NOW PUT THE ML INTO : " : P
      RINT"{DOWN}DECIMAL"B"-"C-1 : PRINT"
      {DOWN}{4 SPACES}HEX ";
63994 K = 4096 : H = B : GOSUB63997 : PRINT" - "; : H
      = C : GOSUB63997 : PRINT
63995 PRINT"{DOWN}THEN DELETE LINES 63991
      -63997.{DOWN}" : D = INT(C/J)
63996 POKE2,D : POKE1,C-J * D : POKE45,PEEK(1) :
      POKE46,PEEK(2) : CLR : ZZ% = -9510 : END
63997 H = H/K : FORI = 1TO4 : H% = H : H$ = CHR$(48 + H%-
     (H%>9) * 7) : PRINTH$; : H = 16 * (H-H%) : NEXT
63998 REM * 63999 FINDS ML START ADDR
63999 ML = PEEK(61) + 256 * PEEK(62) + 31 : RETURN

Program 3:

Combining BASIC And ML On PET/CBM

63990 REM UPGR/4.0 ROM PET/CBM VERSION
63991 GOSUB63999 : IFPEEK(ML-1) + PEEK(ML-2) +
      PEEK(ML-3)THENPRINT"63999 IS BAD" : E
      ND
63992 INPUT"{CLR}# BYTES TO RESERVE FOR M
      L";A : J = 256 : B = PEEK(42) + J * PEEK(43) : C =
      A + B
63993 PRINT"{DOWN}NOW PUT THE ML INTO : " : P
      RINT"{DOWN}DECIMAL"B"-"C-1SPRINT"
      {DOWN}{4 SPACES}HEX ";
63994 K = 4096 : H = B : GOSUB63997 : PRINT" - "; : H
      = C : GOSUB63997 : PRINT
63995 PRINT"{DOWN}THEN DELETE LINES 63991
      -63997.{DOWN}" : D = INT(C/J)
63996 POKE2,D : POKEl,C-J * D : POKE42,PEEK(1) :
      POKE43,PEEK(2) : CLR : ZZ% = -9510 : END
63997 H = H/K : FORI = 1TO4 : H% = H : H$ = CHR$(48 + H%-
      (H%>9) * 7) : PRINTH$; : H = 16 * (H-H%) : NEXT
63998 REM * 63999 FINDS ML START ADDR
63999 ML = PEEK(58) + 256 * PEEK(59) + 31 : RETURN