Relocatable Jumps#
General Information
Author: Peter Meyer
Assembler: generic
Published: APPLE Assembly Line 12/82
Download: Apple Assembly Line Archive
A machine language routine is said to be relocatable if it can function properly regardless of its absolute location in memory. If a routine contains a JMP or a JSR to an INTERNAL address then it is not relocatable; if it is run in another part of memory then the internal JSR or JMP will still reference the former region of memory. JMPs and JSRs to subroutines at absolute locations (e.g. in the Monitor) do not impair the relocatability of a routine.
I will explain here a technique whereby you can, in effect, perform internal JSRs and JMPs without impairing the relocatability of your routine. There is a small price to pay for this: namely, an increase in the length of your routine. Your routine must be preceded by a 2-part Header Routine which is 43 bytes long. In addition, each internal JSR requires 8 bytes of code, and each internal JMP requires 11 bytes of code.
No tables or other data storage are required, except that three bytes must be reserved for a JMP instruction. These three bytes can be anywhere in memory, but must be at an absolute location. There are three bytes that normally are used only by Applesoft, namely, the ampersand JMP vector at $3F5 to $3F7. Since we are here concerned only with machine language routines in their own right, we can use the locations $3F5 to $3F7 for our own purposes. However, other locations would do just as well.
The technique is fully illustrated in the accompanying assembly language program. This routine consists of three parts:
- Header Part 1 (SETUP), which sets up a JMP instruction at VECTOR (at $3F5-$3F7, but could be different, as explained above) to point to Header Part 2.
- Header Part 2 (HANDLER), which is a 15-byte section of code whose task is to handle requests to perform internal JSRs and JMPs (more on this below).
- The main part of the routine, in which internal JSRs and JMPs (in effect) are performed using macro instructions.
When your routine (including the Header) is executed, the first thing that happens is that Header Part 1 locates itself (using the well-known JSR $FF58 technique), then places a JMP HANDLER at VECTOR. Thereafter a JMP VECTOR is equivalent to JMP HANDLER, and a JSR VECTOR is equivalent to a JSR HANDLER. The HANDLER routine handles requests from your routine for internal JSRs and JMPs. To perform a JSR to an internal subroutine labelled SUBR simply include the following code:
HERE LDA #SUBR-HERE-7 low byte of offset LDY /SUBR-HERE-7 high byte of offset TSX JSR VECTOR
As explained above, the JSR VECTOR is in effect a JSR HANDLER. The Header Part 2 code takes the values in the A and Y registers and adds them to an address which it obtains from the stack to obtain the address of SUBR. It then places this address in INDEX ($5E,5F) and executes "JMP (INDEX)".
An internal JMP, from one part of your routine to another, is performed in a similar manner. Suppose you wish to JMP from HERE to THERE. It is done as follows:
HERE LDA #THERE-HERE-7 low byte of offset LDY /THERE-HERE-7 high byte of offset TSX JSR $FF58 JMP VECTOR
Since we are (in effect) performing a JMP, rather than a JSR, we do a JMP VECTOR rather than a JSR VECTOR. The other difference is that we have a JSR $FF58 following the TSX.
Clearly the sequence of instructions which allows you to perform a JMP or a JSR could be coded as a macro. The macros to use are shown in the accompanying program listing. By using macros an internal JMP or JSR can be performed with a single macro instruction bearing a very close resemblance to a real JSR or JMP instruction.
The following program, which consists of the Header Routine plus a demonstration routine, can be assembled to disk using the .TF directive. It can then be BRUN at any address and it will function properly. Thus it is relocatable, despite the fact that there are (in effect) an internal JMP and two internal JSRs performed.
When performing an internal JSR or JMP using my techniques, it is not possible to pass values in the registers, since these are required to pass information to the HANDLER routine. Nor is it advisable to try to pass parameters on the stack, even though the HANDLER routine does not change the value of the stack pointer. Better is to deposit values in memory and retrieve them after the transition has been made.
The HANDLER routine passes control to the requested part of your routine using a JMP indirect. (INDEX at $5E,5F, has been used in the accompanying program, but any other address would do as well, provided that it does not cross a page boundary.) This means that the section of your routine to which control is passed (whether or not it is a subroutine) may find its own location by inspecting the contents of the location used for the JMP indirect. This feature of this technique is also illustrated in the accompanying program, in the PRINT.MESSAGE subroutine.
The use of internal data blocks is something not normally possible in a relocatable routine, but it can be done if the techniques shown here are used.
This method of performing internal JSRs and JMPs in a relocatable routine may be simplified if the routine is intended to function as a subroutine appended to an Applesoft program. If the subroutine is appended using my utility the Routine Machine (available from Southwestern Data Systems), then it is not necessary to include the 47-byte Header Routine. Internal JMPs and JSRs can still be performed exactly as described above, except that the address of VECTOR must be $3F5-$3F7. This technique is not described in the documentation to the Routine Machine. A full explanation may be found in the Appendix to the documentation accompanying Ampersoft Program Library, Volume 4 (also available from Southwestern Data Systems).
1010 .TF B.MEYER.1 1020 *SAVE S.MEYER.1 1030 *-------------------------------- 1040 * SETUP AND HANDLER ROUTINES 1050 * TO ALLOW INTERNAL JSRS AND 1060 * JMPS IN A RELOCATABLE MACHINE 1070 * LANGUAGE ROUTINE 1080 1090 * BY PETER MEYER, 11/3/82 1100 *-------------------------------- 1110 * LOCATIONS 1120 1130 INDEX .EQ $5E,5F 1140 STACK .EQ $100 - $1FF 1150 VECTOR .EQ $3F5 - $3F7 1160 *-------------------------------- 1170 * MACRO DEFINITIONS 1180 1190 .MA JSR 1200 :1 LDA #]1-:1-7 1210 LDY /]1-:1-7 1220 TSX 1230 JSR VECTOR 1240 .EM 1250 1260 .MA JMP 1270 :1 LDA #]1-:1-7 1280 LDY /]1-:1-7 1290 TSX 1300 JSR $FF58 1310 JMP VECTOR 1320 .EM 1330 *-------------------------------- 1340 * HEADER PART 1 1350 1360 SETUP JSR $FF58 FIND OURSELVES 1370 TSX 1380 CLC 1390 LDA #HANDLER-SETUP-2 1400 .DA #$7D,STACK-1 FORCE ABS,X MODE 1410 STA VECTOR+1 1420 1430 LDA /HANDLER-SETUP-2 1440 ADC STACK,X 1450 STA VECTOR+2 1460 1470 LDA #$4C "JMP" 1480 STA VECTOR 1490 BNE MAIN.ROUTINE ALWAYS 1500 *-------------------------------- 1510 * HEADER PART 2 1520 1530 HANDLER 1540 1550 * ON ENTRY A,Y HOLD OFFSET 1560 * FOR JMP OR JSR FROM ROUTINE 1570 * X IS STACK POINTER FROM BEFORE LAST JSR 1580 1590 CLC 1600 .DA #$7D,STACK-1 FORCE ABS,X MODE 1610 STA INDEX 1620 TYA 1630 ADC STACK,X 1640 STA INDEX+1 1650 JMP (INDEX) 1660 *-------------------------------- 1670 * MAIN ROUTINE, FOR EXAMPLE 1680 *-------------------------------- 1690 MSG .EQ $06 AND $07 1700 CH .EQ $24 1710 CV .EQ $25 1720 INVFLG .EQ $32 1730 COUNT .EQ $3C 1740 SETTXT .EQ $FB39 1750 VTABZ .EQ $FC24 1760 HOME .EQ $FC58 1770 COUT .EQ $FDED 1780 *-------------------------------- 1790 MAIN.ROUTINE 1800 JSR SETTXT 1810 JSR HOME 1820 MAIN.LOOP 1830 LDA #190 1840 STA COUNT 1850 .1 LDA #AALQT-PRINT.MESSAGE 1860 STA MSG 1870 LDA /AALQT-PRINT.MESSAGE 1880 STA MSG+1 1890 >JSR PRINT.MESSAGE 1900 DEC COUNT 1910 BNE .1 1920 LDA #LONGQT-PRINT.MESSAGE 1930 STA MSG 1940 LDA /LONGQT-PRINT.MESSAGE 1950 STA MSG+1 1960 >JSR PRINT.MESSAGE 1970 >JMP FORWRD 1980 1990 *-------------------------------- 2000 PRINT.MESSAGE 2010 CLC 2020 LDA MSG CHANGE RELATIVE ADDRESS TO 2030 ADC INDEX AN ABSOLUTE ADDRESS, BY 2040 STA MSG ADDING THE OFFSET 2050 LDA MSG+1 2060 ADC INDEX+1 2070 STA MSG+1 2080 LDY #0 POINT AT FIRST CHAR OF MSG 2090 .1 LDA (MSG),Y GET NEXT CHAR OF MSG 2100 BMI .2 IT IS LAST CHAR 2110 ORA #$80 MAKE APPLE VIDEO FORM 2120 JSR COUT PRINT IT 2130 INY ADVANCE POINTER 2140 BNE .1 ...ALWAYS 2150 .2 JMP COUT PRINT AND RETURN 2160 *-------------------------------- 2170 * 256 BYTES TO JUMP OVER, JUST FOR ILLUSTRATION 2180 2190 .BS $100 2200 *-------------------------------- 2210 * TOGGLE INVERSE FLAG, AND HOME CURSOR 2220 2230 FORWRD LDA INVFLG 2240 EOR #$C0 2250 STA INVFLG 2260 LDA #0 2270 STA CH 2280 STA CV 2290 JSR VTABZ 2300 >JMP MAIN.LOOP 2310 *-------------------------------- 2320 AALQT .AT /AAL / 2330 LONGQT .HS 0D0D 2340 .AS / A P P L E A S S E M B L Y L I N E / 2350 .HS 0D02 2360 .AT / S - C S O F T W A R E C O R P . /