Relocatable Machine Code

Any discussions related to the creation of new hardware or software for the ZX80 or ZX81
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Relocatable Machine Code

Post by David G »

This morning as i was waking up, a thought occurred to me: "i really should make my machine code monitor program relocatable". It is currently in two versions: one that runs from the REM, and another then runs from above RAMTOP (so that you can LOAD another program, and still have the monitor loaded). But having one version would be more managable

A quick search on google led me to this article from SyncWare News Volume 2 Number 2 pages 15-16
MACHINE CODE TOPICS

How to Write Relocatable Z80 Code

Peter D. Hoffman
Delphic Enterprises
5618 Martinique Dr.
Corpus Cristi, Tx. 78411

When Delphic Enterprises decided to write some some programming utility routines (renumber, string search, etc.), we made the decision that the code should be relocatable. Relocatable means that the code will execute, without modification, when placed at any available address. We found that carrying this out technically, was not a trivial problem. The techniques that had to be developed to make the code completely relocatable are the subject of this first article.

This is the first in a planned series on advanced machine code techniques on the ZX/TS computers. I will assume that you have an understanding of the Z80 machine code instruction set and some knowledge of the Sinclair specific topics such as organization of storage, system variables, character set and some acquaintance with the ROM.

I strongly recommend that you obtain the "Sinclair ZX81 ROM Disassembly" book by Dr. Ian Logan. "Machine Code programming on the Sinclair," By Toni Baker, is another good book. This is one of the most under-appreciated books in this area. If you can work through this book, including all the exercises, you will know how to program in machine code when you are finished.

I will begin by reviewing some of the instructions that are available in the Z80 set that might assist in making the programming relocatable. In particular, the instructions for transferring execution program execution to somewhere, other than the next sequential instruction, will be the key to achieving relocatable code. These are jumps and calls.

Jump Relative

The instruction that is probably most familiar, is the jump relative, JR, either conditional or unconditional. The limitation, of course, is that this jump can only be (effectively) +127 or -128 from the address of the next instruction, which is in the program counter (PC). It is possible, although awkward, to overcome this limitation by stringing several JR commands at intervals of slightly less than 128 bytes through your machine code. The result would be relocatable.

The absolute jump instruction, also conditional or unconditional, will allow the execution to be transferred within the normal 64K address space of the Z80. We can use these instructions to jump to fixed address routines, such as those within the ROM. They would not be useful in jumping to a routine that is relocatable.

Jump (HL)

There is a variation of the JP instruction that is useful, as we shall see. The JP (HL) instruction will jump to the location contained in the HL register pair. There are also analogous instructions, JP (IY) and JP (IX). If the relocatable address can be developed in the HL registers, then the JP (HL) instruction will allow us to have relocatable code.

We can develop relocatable code that will jump, by use of the JP (HL) instruction. If we have some sort of base address (similar to the index addressing mode), then all jumps could be made relative to that base address. By adding a base address and an offset in the HL register, the correct relocatable address would be available for the JP (HL). The base address is the only item that would vary, depending on where the code is located.

Let the base address be the entry point into our machine code and the same as the first byte of a block of machine code. For simplicity, this will be 2000h, although it doesn't have to be. Once the code is written, the base address could be any address that is allowed on the particular computer system.

Call Subroutine

The CALL instruction, both conditional and unconditional, has only the absolute addressed version. This instruction can be used to call fixed routines, such as those within the ROM. There is no CALL instruction that will call an address in a register pair like JP (HL).

This brings us to the major problem in the way of writing relocatable code for the Z80; CALLing your own relocatable subroutines. The Z80 opcode, CALL, allows only absolute addresses. Ideally, we would like to have an instruction, something like CALL (iY+dd), where the displacement could be more than +/- 128, but such an instruction does not exist. The next best solution is to have an instruction such as CALL (HL). The key point here is that we want the action that the CALL instruction provides; i.e., the address of the next instruction in the PC is pushed onto the stack, so that a later return will bring execution back to the same place, while the CALL (HL) instruction doesn't exist either, the JP (HL) instruction will at least get to the subroutine. The problem is then to get back when the subroutine is finished.

A solution shown below is to develop the addresses for where the "call" is to jump to (the subroutine) and the return. For the example, we assume the base address of 2000h is in the BC register pair.

Code: Select all

LD HL, 0xxx   offset for the return address
ADD HL,BC     add offset and base address
PUSH HL       stack address fo return
LD HL, 0yyy   offset for subroutine entry 
ADD HL, BC    add subroutine offset and base 
JP (HL)       jump to subroutine
Execution of these lines causes the computer to JUMP to a subroutine the address of which is the BASE plus the OFFSET (0yyy--stored in HL). At the conclusion of the subroutine, the RET instruction POPs the return address (BASE plus 0xxx) off the stack and jumps back to the original code.

This coding does result in relocatable but awkward code. If the coding moves relative to the base address, then all of the offsets must be changed. This occurs a lot as code is debugged. One strategem [sic] is to put thoroughly debugged subroutines out of the way at the beginning or end of the planned address space. This way there is no movement of the subroutine relative to the base address, and the offset will not change. This still leaves the problem of the changing offset for the return address.

Call to Jump (HL)

What we would really like to have, is to automatically stack the correct return address and make the JP (HL). Fortunately, there is a solution that will accomplish this objective. Residing at several places in the ROM (i.e., at fixed addresses) are JP (HL) instructions. For instance, there is a JP (HL) instruction at the address 0044h (this location is not available for those using the video upgrade noted elsewhere in this issue. Try 0ACAh for TS1000 and 1264h for 2068 machines). Thus the following piece of code will accomplish the objective of being relocatable.

Code: Select all

LD HL, 0yyy   offset for the subroutine entry
ADD HL, BC    add offset and base address
CALL 0044     jump to subroutine
This coding will automatically stack the address following the CALL for the eventual return, and then make the jump to the subroutine via the JP (HL) instruction located at 0044h. More importantly, this coding does not have to be modified every time it moves relative to the base address, as long as the subroutine location remains fixed in offset (as previously recommended).

The BASE Address... How to Find It

On the Sinclair computer, the machine code routine is entered by a command such as, PRINT USR or RAND USR, followed by an address. This address ends up in the BC register pair, and is available on entering your machine code routine. This becomes our entry; the base address from which all addresses are referenced for repeatability. This address can be used immediately, pushed onto the stack or placed in a memory location for later retrieval. There are a number of memory locations that are relatively secure. I say relatively, since more and more programs are using identical storage locations and conflicts do develop.

...And Where to Store It

Most TS [Timex/Sinclair] machine coders, of course, know about the "unused" locations in the system variable area. These locations are 4021h ["not used" (1 byte)/UNUSED1], 407Bh and 407Ch [16507 & 16508 "not used" (2 bytes)/UNUSED2]. Less well known, are several other locations that can also be used without interference with or by the 8K operating system. The sixth (mem-5) calculator register [*within MEMBOT], located at 4076h to 407Ah is not used by the operating system. It apparently was a spare for future ROMs. Values can be placed there without problems. This is the area (4079h and 407Ah) that I chose to place my entry/base address for the relocatable machine code that I wrote.

Another location that can be used with impunity is 4009h, known as VERSN. This was a system variable intended to provide identification of various versions of modified or expanded ROMs. This was never utilized, and in fact, the current operating system never reads or writes to that location, other than initially setting it to zero.

The above locations can be used at any time without disturbing or being distrubed [sic] by the operating system. Furthermore, as long as you stay within your own machine code program, there are quite a few of the other variables that can also be used for temporary storage. The operating system will simply write over them later.

A problem can arise when another program, in the machine at the same time, uses the same locations. This happened when using Ray Kingsley's HOT Z program to test my own machine code program. The single stepper mode of that program uses the same mem-5 register for variables. I had to be very careful NOT to execute any instruction that loaded to mem-5 locations when single stepping or a crash would occur.

Now you may ask, why not keep the base address in the IX or IY registers rather than in a pair of system variables if there are potential conflict problems? The difficulty with this approach is that the IX and IY registers are already spoken for in the operating system. The IX register is used during the SLOW display. If your machine code never switches to SLOW mode (no pauses, input or display), then you could use the IX register. Using the IY register is more feasible. The IY register normally contains the address 4000h. This makes for compact reference to single bytes of the system variables, by use of commands that use the indexed addressing mode such as, BIT 7, (IY+d), or LD (IY+d),nn, and so forth. In my own case, since I was making extensive use of the system variables, it was useful to retain the IY register containing 4000h, in order to address those variables. If you use the IY. register, it must be reset to 4000h upon exiting the machine code routine. This is most easily done by exiting through the "STACK-BC" routine at 1520h, which is what the USR function does.

Finally, let me address the problem of reading and writing data values imbedded in your code that must be rendered relocatable. I personally did not have the problem of writing to relocatable locations, since our software is epromable, and therefore you can't write to it. The same strategem applies here as for the jumps and calls, i.e., all addresses must be made relative to some base address, which necessitates writing the code to use (HL) address referencing, such as LD r, (HL).
* I did not know that MEMBOT is divided into six regions. I'm guessing it is layed out like this:

Code: Select all

MEMBOT
      hex  decimal
mem-0 405D 16477 (5 bytes)
mem-1 4062 16482 (5 bytes)
mem-2 4067 16487 (5 bytes)
mem-3 406C 16492 (5 bytes)
mem-4 4071 16497 (5 bytes)
mem-5 4076 16502 (5 bytes)
Very nice to know that the last 5 bytes are unused by the BASIC ROM

I'll have to try this
dr beep
Posts: 2077
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Relocatable Machine Code

Post by dr beep »

You need to know where your routine is loaded and then alter all JP/CALL and redaing of memory in the program. Last can be done with storage outside the program.

At start place a

XOR A
INC A : A not zero
CALL #AC8

HL now holds start of code +5. Together with original start you can calculate all displacements.
User avatar
mrtinb
Posts: 1911
Joined: Fri Nov 06, 2015 5:44 pm
Location: Denmark
Contact:

Re: Relocatable Machine Code

Post by mrtinb »

MEM-1 to MEM-5 is the 5 variables available for the Floating Point Calculator of the Basic ROM. However the commands in the Floating Point Calculator never needed five variables at the same time, so MEM-5 is free for your disposal.
Martin
https://zx.rtin.be
ZX81, Lambda 8300, Commodore 64, Mac G4 Cube
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: Relocatable Machine Code

Post by David G »

Excellent information. thanks Martin & dr beep

In the ROM at 0AC8h (UNSTACK-Z+3):

Code: Select all

       POP HL
       RET Z
       JP  (HL)
What does "redaing of memory" mean?
User avatar
mrtinb
Posts: 1911
Joined: Fri Nov 06, 2015 5:44 pm
Location: Denmark
Contact:

Re: Relocatable Machine Code

Post by mrtinb »

Thanks for the input Drbeep & David. That’ll make my relocatable code routine better.
Martin
https://zx.rtin.be
ZX81, Lambda 8300, Commodore 64, Mac G4 Cube
dr beep
Posts: 2077
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Relocatable Machine Code

Post by dr beep »

An example of how something could work.

Code: Select all


	org	40000		; somewhere in memory to test
				; this code is loaded at 20000

start	xor	a
	inc	a
	call	#ac8
; above can also be replaced by PUSH BC POP HL when a RAND USR is used
; or LD H,B LD L,C


here	ld 	de,allcall-here
	add	hl,de		; HL now holds relocated "ALLCALL"
	exx			; keep in HL'

; the relocated call
	exx			; go to shadowregisters
	call    #aca		; CALL (HL) but we will do CALL PRINT
	dw	print-allcall	; the displaced value of the routine

	exx
	call    #aca		; CALL (HL) but we will do CALL PRINT
	dw	print-allcall	; the displaced value of the routine


	ret

allcall	ld	d,h		; save HL' in DE'
	ld	e,l
	pop	hl		; get return
	ld	c,(hl)		
	inc 	hl
	ld	b,(hl)		; BC holds displacement from HL
	inc 	hl
	push	hl		; stack return
	ld	h,d		; get HL'
	ld	l,e
	add	hl,bc		; calculate displaced routine
	push	hl		; on stack
	ex	de,hl		; repair HL'
	exx			; back to main
	ret			; go to your routine

print	ld	a,38		; a simple routine to test
	rst	16
	ret

User avatar
mrtinb
Posts: 1911
Joined: Fri Nov 06, 2015 5:44 pm
Location: Denmark
Contact:

Re: Relocatable Machine Code

Post by mrtinb »

I tried to make an elegant solution as well.

Here you call with RAND USR, so current address comes into BC.

To make a call to SUBRUTINE, you just move BC to HL, and prefix your call with CALL DYNAMIC.

BC has to be preserved in the program.

Image
Martin
https://zx.rtin.be
ZX81, Lambda 8300, Commodore 64, Mac G4 Cube
dr beep
Posts: 2077
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Relocatable Machine Code

Post by dr beep »

I shortened it

Code: Select all



	org	40000		; somewhere in memory to test
				; this code is loaded at 20000

start	xor	a
	inc	a
	call	#ac8		
here	ex	de,hl		; save displacement in DE
	exx			; keep in HL'


; the relocated call
loop	exx			; go to shadowregisters for DE'
	ld 	hl,print-here
	add	hl,de		; HL now holds relocated "PRINT" routine
	call    #aca		; CALL (HL)

	ld hl,#4034		; frames
	ld a,(hl)
	sub 5
wfr	cp (hl)
	jr nz,wfr

	jr	loop


print	exx			; back to main
	ld hl,(#400c)
	inc hl
	inc hl
	inc hl
	ld a,(hl)
	inc a
	and 63
	ld (hl),a
	ret


User avatar
stefano
Posts: 567
Joined: Tue Dec 11, 2012 9:24 am
Contact:

Re: Relocatable Machine Code

Post by stefano »

The 'supercode' tools for the Spectrum used to discover the current location by calling a known position in ROM with a simple RET instruction. After that they got the address from the stack.
dr beep
Posts: 2077
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Relocatable Machine Code

Post by dr beep »

stefano wrote: Mon Jul 25, 2022 8:53 am The 'supercode' tools for the Spectrum used to discover the current location by calling a known position in ROM with a simple RET instruction. After that they got the address from the stack.
But then no intrupt must appear after the RET in ROM.
Post Reply