Indirect calls with the Z80

Page 1/4
| 2 | 3 | 4

By DarkSchneider

Paladin (798)

DarkSchneider's picture

06-04-2018, 10:20

Looking at the Z80 instruction set, we have indirect jumps JP (HL/IX/IY) but not indirect calls.

The "logical" way to do an indirect call probably is, looking at this, to do:

push returning address
ld hl, function_pointer
jp (hl)
returning_address:
... code ...

But I saw another interesting and even better way through a tricky way, that is using the RET instruction.

push returning_address
push function_pointer
ret
returning_address:
... code ...

It uses the RET behavior that pops to PC for the first indirect call. Then the function pointer will get the returning_address from the stack when returning.

See that by this way we have all the registers free for passing parameters, and also we can even do conditional call using the RET cc intruction.

Login or register to post comments

By theNestruo

Expert (93)

theNestruo's picture

06-04-2018, 13:38

I have this function:

; Simply "jp [hl]", but can be used to emulate the instruction "call [hl]"
; param hl: address
JP_HL:
	jp	[hl]

So I simply call JP_HL.
It also works with conditional jumps and calls (but I think I have never used it that way).

By Grauw

Enlighted (8069)

Grauw's picture

06-04-2018, 13:53

Yep, I also use that method.

One thing I would wish for in the Z80 instruction set is if the indexed register indirect jump instructions also had offset support. jp ix + o would’ve really made polymorphic calls easier and cheaper to use. Now I always have to put the polymorphic call at the start of the object, but if you got more than one of them you have to fall back on more expensive methods.

By NYYRIKKI

Enlighted (5283)

NYYRIKKI's picture

06-04-2018, 13:50

I think all of these are pretty common tricks... I've even once seen stack used like:

	DI
	LD SP,TABLE
	RET

TABLE:	DW CODE1,CODE1,CODE2,CODE1,ALLDONE

CODE1:	do something
	RET
CODE2:	do something else
	RET

ALLDONE:
	LD SP,XYZ
	.
	.
	.

Now to mess up with your head even a bit more...
RST C,#38 == #38 #FF

Wink

By DarkSchneider

Paladin (798)

DarkSchneider's picture

06-04-2018, 15:50

Grauw wrote:

Yep, I also use that method.

One thing I would wish for in the Z80 instruction set is if the indexed register indirect jump instructions also had offset support. jp ix + o would’ve really made polymorphic calls easier and cheaper to use. Now I always have to put the polymorphic call at the start of the object, but if you got more than one of them you have to fall back on more expensive methods.

Not sure if I mean the same, but you could use an addressing table at an address with the low part equal to 0 (##00h) and then select by:

ld hl, table
ld a, function_index
sla a
ld l, a
jp (hl)

Up to 128 functions could be pointed.

By DarkSchneider

Paladin (798)

DarkSchneider's picture

06-04-2018, 17:19

I forgot, the more generic way would be like (i.e. using the RET method):

ld de, table ; table base address
ld hl, (function_index) ; variable containing the function index to call
ld h, 0 ; if function_index is 8-bit
add hl, hl ; x2
add hl, de ; index from table begin
push hl ; set call address
ret ; indirect call

So table could be placed at any address.

By bore

Expert (115)

bore's picture

06-04-2018, 17:36

theNestruo wrote:

I have this function:

; Simply "jp [hl]", but can be used to emulate the instruction "call [hl]"
; param hl: address
JP_HL:
	jp	[hl]

So I simply call JP_HL.
It also works with conditional jumps and calls (but I think I have never used it that way).

As mentioned above it can be extended for all register pairs.

JP_HL:
	jp	[hl]
JP_BC:
	push bc
	ret
JP_DE:
	push de
	ret
JP_IX:
	jp	[ix]
JP_IY:
	jp	[iy]
JP_HL_S:
	exx
	push hl
	exx
	ret
JP_BC_S:
	exx
	push bc
	exx
	ret
JP_DE_S:
	exx
	push de
	exx
	ret

Could be convenient to have if you have callbacks that takes arguments in hl or other registers.
Tricky part is when your callback takes arguments in all registers, then it is probably easiest to resort to rewriting the call instruction. (Probably the faster option for everything but [hl])

By Grauw

Enlighted (8069)

Grauw's picture

06-04-2018, 17:45

@bore Ah yes nice, those JumpBC and JumpDE are going into my lib as well Smile.

By theNestruo

Expert (93)

theNestruo's picture

06-04-2018, 19:03

DarkSchneider wrote:

Not sure if I mean the same, but you could use an addressing table at an address with the low part equal to 0 (##00h) and then select by:

ld hl, table
ld a, function_index
sla a
ld l, a
jp (hl)

Up to 128 functions could be pointed.

DarkSchneider wrote:

I forgot, the more generic way would be like (i.e. using the RET method):

ld de, table ; table base address
ld hl, (function_index) ; variable containing the function index to call
ld h, 0 ; if function_index is 8-bit
add hl, hl ; x2
add hl, de ; index from table begin
push hl ; set call address
ret ; indirect call

So table could be placed at any address.

Are you sure about that code? Unless I'm missing something, it ends up executing the code at hl, not the code at the address pointed by hl...
For example: in a single entry table: dw $0000 it won't jump to $0000; it will execute two NOPs and then whatever is after the table...

My jump table routines actually look like this:

; -----------------------------------------------------------------------------
; Uses a jump table. Usage: both call JP_TABLE and jp JP_TABLE are valid
; param hl: jump table address
; param a: unsigned 0-based index (0, 1, 2...)
JP_TABLE:
	add	a ; a *= 2
; ------VVVV----falls through--------------------------------------------------

; -----------------------------------------------------------------------------
; Uses a jump table. Usage: both call JP_TABLE_2 and jp JP_TABLE_2 are valid
; param hl: jump table address
; param a: unsigned 0-based index (0, 2, 4...)
JP_TABLE_2:
	add	l ; hl += a
	ld	l, a
	adc	h
	sub	l
	ld	h, a
	; jr	JP_HL_INDIRECT ; (falls through)
; ------VVVV----falls through--------------------------------------------------

; -----------------------------------------------------------------------------
; Emulates the instruction "jp [[hl]]" or "call [[hl]]"
; param hl: pointer to the address
; touches a
JP_HL_INDIRECT:
	ld	a, [hl] ; hl = [hl]
	inc	hl
	ld	h, [hl]
	ld	l, a
	; jr	JP_HL ; (falls through)
; ------VVVV----falls through--------------------------------------------------

; -----------------------------------------------------------------------------
; Simply "jp [hl]", but can be used to emulate the instruction "call [hl]"
; param hl: address
JP_HL:
	jp	[hl]
; -----------------------------------------------------------------------------

By NYYRIKKI

Enlighted (5283)

NYYRIKKI's picture

06-04-2018, 20:31

DarkSchneider wrote:
ld hl, table
ld a, function_index
sla a
ld l, a
jp (hl)

Up to 128 functions could be pointed.

No, it doesn't work quite like that... Although the assigned mnemonic is "JP (HL)" the functionality is anyway "JP HL"

By santiontanon

Paladin (787)

santiontanon's picture

06-04-2018, 20:33

I think you are right theNestruo!!

I'm a little lazy to calculate it now, but it'd also be nice to calculate how large should the jump table in order to justify this vs just a series dec/jr like:
ld a,(function_index)
or a
jr z,FUNCTION1
dec a
jr z,FUNCTION2
...
jr FUNCTIONn

I'm assuming that for 4 - 5 functions, just the list of "dec/jr z" is faster.

Page 1/4
| 2 | 3 | 4