!!!Relocatable Jumps

General Information

Author: Peter Meyer \\
Assembler: generic \\
Published: APPLE Assembly Line 12/82 \\
Download: [Apple Assembly Line Archive|http://salfter.dyndns.org/aal.shtml] \\

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 .  /
}}}