Indirect calls with the Z80

Page 3/4
1 | 2 | | 4

By Grauw

Ascended (8508)

Grauw's picture

07-04-2018, 04:43

MOA wrote:

Thanks Smile Nice reading material for me tomorrow, as I should hit the sack. Are you up - like me - so late, or up very early? hehe Wink

So late Crying. Made some nice progress again on the game though Smile. Maybe a vid tomorrow.

By DarkSchneider

Paladin (880)

DarkSchneider's picture

07-04-2018, 08:58

Right I forgot to load the memory from HL and then push it. Can't edit now oO Put here:

; for table with address like ##00H
ld de, table
ld a, function_index
sla a
ld e, a
ld hl, (de)
jp (hl)
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
ld hl, (hl) ; load the call address from table
push hl ; set call address
... set all the parameters in any registers here ...
ret ; indirect call

As note, I think the ones not using JP (HL) are better options and, IMO the RET one is the best one, as you get all the registers free for passing parameters. Once you push the call address, you can use also HL and A and all others before the RET, while not pushing any other value of course.

@Grauw the last method I put is not size dependant, it can take for few methods some more time, but is a fixed time always. Then depending the case it could be preferable.

Also, using a "jumping table" seems an interesting idea, it would be a table with items like:

struct {
BYTE jmp_opcode; // DO NOT touch
ADDRESS address;
}

Simply setting each jmp to the JP opcode on array initialization. This would allow even dynamic tables instead only static ones.

By santiontanon

Paladin (846)

santiontanon's picture

07-04-2018, 17:42

@Artrag! Oh, I like that version better!! didn't think of using the djnz instruction for that purpose!!!

By hit9918

Prophet (2868)

hit9918's picture

07-04-2018, 22:00

; in: a = func (0..127), ix = this
ld a, function_index

function indexes are a snail!
the function index business is coming from BASIC ON GOTO
in C asm the OO thing is a matter of simple compile time constant offsets
then this example

call_virtual:
    add a,(ix+0)
    ld h,(ix+1)
    ld l,a
    jr nc,$+3
    inc h
    jp (hl)

collapses to a simple

        mymethod EQU 23*2        ;compile time multiply

        ld l,(ix+mymethod+0)
        ld h,(ix+mymethod+1)
        call jp_hl

By MOA

Champion (293)

MOA's picture

08-04-2018, 03:20

@hit9918: you are missing the indirection... the first two bytes of the object contain a pointer to the virtual table. The object doesn't contain the virtual table itself. So your code is not doing the same thing.

(Thinking about it my original code was probably broken: the virtual table shouldn't be some address table because jp (hl) just jumps to hl, not (hl)... misleading name. So yeah function index should be premultiplied by 3 and virtual table should be a table of jumps, not a table of addresses).

You do have a point though: the function index should be constant/known at compile time so can be further optimized probably. Don't see an opportunity in this tiny scope though.

I hope that at some point Z80 assemblers also add some optional optimization pass where they can do basic optimizations like:
- inlining
- constant folding
- loop unrolling

By MOA

Champion (293)

MOA's picture

08-04-2018, 05:33

So I probably wasted my time, beating a dead horse... I put together a very simple virtual function simulation for a GameObject (base class) and a SpriteObject (derived class) in Z80 asm. Didn't test it or even assemble the code so the syntax might be slightly off.

; -- GameObject

GAME_OBJ_FLAG_DESTROYED: EQU 1 << 0	

GameObject: MACRO
	.VTable: ds 2
	.flags: ds 1
	ENDM

; -- GameObject VTable

GameObjectVTable:
	jp GameObjectOnUpdate  ; function called most often should be on top
	jp GameObjectOnHit
	jp GameObjectDestruct
	
; virtual version should only be called when type of object is unknown

VirtualGameObjectOnUpdate:
	ld l,(ix+0)
	ld h,(ix+1)
	jp (hl)
	
VirtualGameObjectOnHit:
	ld a,(ix+0)
	ld h,(ix+1)
	add a,3
	ld l,a
	jr nc,skip
	inc h
skip:
	jp (hl)

VirtualGameObjectDestruct:
	ld a,(ix+0)
	ld h,(ix+1)
	add a,6
	ld l,a
	jr nc,skip
	inc h
skip:
	jp (hl)
	
; -- GameObject implementation

; ix = this
GameObjectConstruct:
	ld hl,GameObjectVTable
GameObjectConstructOverrideVTable:
	ld (ix+.VTable+0),l
	ld (ix+.VTable+1),h
	ld (ix+.flags),0
	ret
	
GameObjectOnUpdate:
GameObjectOnHit:
GameObjectDestruct:
	; nothing to do... wasting cycles :(
	ret
	
; -- SpriteObject

SpriteObject: MACRO
	GameObject
	.x: ds 1
	.y: ds 1
	ENDM

; -- SpriteObject VTable
	
SpriteObjectVTable:
	jp SpriteObjectOnUpdate
	jp SpriteObjectOnHit
	jp GameObjectDestruct
	
; -- SpriteObject implementation
	
SpriteObjectConstruct:
	xor a
	ld (ix+.x),a
	ld (ix+.y),a
	ld hl,SpriteObjectVTable
	jp GameObjectConstructOverrideVTable
	
SpriteObjectOnUpdate:
	inc (ix+.x)
	inc (ix+.y)
	jp GameObjectOnUpdate
	
SpriteObjectOnHit:
	; SpawnExplosion
	ld a,GAME_OBJ_FLAG_DESTROYED
	or (ix+.flags)
	ld (ix+.flags),a
	jp GameObjectOnHit
	
; --- test code ---------------------------

obj1: GameObject 
obj2: GameObject 
spr1: SpriteObject 
spr2: SpriteObject 

; the object table
NUM_OBJECTS: EQU 4
Objects: dw obj1, spr1, obj2, spr2 

main:
	; construct objects in object table
	ld ix, obj1
	call GameObjectConstruct
	ld ix, obj2
	call GameObjectConstruct
	ld ix, spr1
	call SpriteObjectConstruct
	ld ix, spr2
	call SpriteObjectConstruct

loop:
	; call virtual update and onhit for all alive objects
	ld b, NUM_OBJECTS
	ld hl, Objects
next:
	push bc
	ld a,(hl)
	inc hl
	ld ixl, a
	ld b,a
	ld a,(hl)
	inc hl
	or b
	jr z,skip ; null pointer
	ld ixh, a
	push hl 
	; stupid test
	call VirtualGameObjectUpdate
	call VirtualGameObjectOnHit
	pop hl
skip:
	pop bc
	djnz next
	
	; destroy alive objects with destroy flag set
	ld b, NUM_OBJECTS
	ld hl, Objects
next2:
	push bc
	ld a,(hl)
	inc hl
	ld ixl, a
	ld b,a
	ld a,(hl)
	inc hl
	or b
	jr z,skip2 ; null pointer
	ld ixh, a
	bit 0,(ix+.flags)
	call nz,destroy ; destroy flag set
skip2:
	pop bc
	djnz next2
		
	jp loop
	ret

destroy:
	push hl
	; set null pointer in object table
	xor a
	dec hl
	ld (hl),a
	dec hl
	ld (hl),a
	; invoke virtual destructor
	call VirtualGameObjectDestruct
	pop hl
	ret

By MOA

Champion (293)

MOA's picture

08-04-2018, 06:15

Now we take a different approach (hit9918's approach basically). It increases object size and makes construction more expensive. Calling virtual functions becomes cheaper (especially when function index > 0). This approach is probably the best if you have enough RAM space.

; -- GameObject

GAME_OBJ_VTABLE_SIZE: EQU 3*2+1

GAME_OBJ_FLAG_DESTROYED: EQU 1 << 0	

GameObject: MACRO
	.VTable: ds GAME_OBJ_VTABLE_SIZE
	.flags: ds 1
	ENDM

; -- GameObject VTable

GameObjectVTable:
	jp GameObjectOnUpdate  ; function called most often should be on top
	dw GameObjectOnHit
	dw GameObjectDestruct
	
; virtual version should only be called when type of object is unknown

VirtualGameObjectOnUpdate:
	jp (ix)
	
VirtualGameObjectOnHit:
	ld l,(ix+3)
	ld h,(ix+4)
	jp (hl)

VirtualGameObjectDestruct:
	ld l,(ix+5)
	ld h,(ix+6)
	jp (hl)
	
; -- GameObject implementation

; ix = this
GameObjectConstruct:
	ld hl,GameObjectVTable
GameObjectConstructOverrideVTable:
	ld d,ixh
	ld e,ixl
	ld bc,GAME_OBJ_VTABLE_SIZE
	ldir
	ld (ix+.flags),0
	ret
	
GameObjectOnUpdate:
GameObjectOnHit:
GameObjectDestruct:
	; nothing to do... wasting cycles :(
	ret
	
; -- SpriteObject

SpriteObject: MACRO
	GameObject
	.x: ds 1
	.y: ds 1
	ENDM

; -- SpriteObject VTable
	
SpriteObjectVTable:
	jp SpriteObjectOnUpdate
	dw SpriteObjectOnHit
	dw GameObjectDestruct
	
; -- SpriteObject implementation
	
SpriteObjectConstruct:
	xor a
	ld (ix+.x),a
	ld (ix+.y),a
	ld hl,SpriteObjectVTable
	jp GameObjectConstructOverrideVTable
	
SpriteObjectOnUpdate:
	inc (ix+.x)
	inc (ix+.y)
	jp GameObjectOnUpdate
	
SpriteObjectOnHit:
	; SpawnExplosion
	ld a,GAME_OBJ_FLAG_DESTROYED
	or (ix+.flags)
	ld (ix+.flags),a
	jp GameObjectOnHit
	
; --- test code ---------------------------

obj1: GameObject 
obj2: GameObject 
spr1: SpriteObject 
spr2: SpriteObject 

; the object table
NUM_OBJECTS: EQU 4
Objects: dw obj1, spr1, obj2, spr2 

main:
	; construct objects in object table
	ld ix, obj1
	call GameObjectConstruct
	ld ix, obj2
	call GameObjectConstruct
	ld ix, spr1
	call SpriteObjectConstruct
	ld ix, spr2
	call SpriteObjectConstruct

loop:
	; call virtual update and onhit for all alive objects
	ld b, NUM_OBJECTS
	ld hl, Objects
next:
	push bc
	ld a,(hl)
	inc hl
	ld ixl, a
	ld b,a
	ld a,(hl)
	inc hl
	or b
	jr z,skip ; null pointer
	ld ixh, a
	push hl 
	; stupid test
	call VirtualGameObjectUpdate
	call VirtualGameObjectOnHit
	pop hl
skip:
	pop bc
	djnz next
	
	; destroy alive objects with destroy flag set
	ld b, NUM_OBJECTS
	ld hl, Objects
next2:
	push bc
	ld a,(hl)
	inc hl
	ld ixl, a
	ld b,a
	ld a,(hl)
	inc hl
	or b
	jr z,skip2 ; null pointer
	ld ixh, a
	bit 0,(ix+.flags)
	call nz,destroy ; destroy flag set
skip2:
	pop bc
	djnz next2
		
	jp loop
	ret

destroy:
	; set null pointer in object table
	push hl
	xor a
	dec hl
	ld (hl),a
	dec hl
	ld (hl),a
	; invoke virtual destructor
	call VirtualGameObjectDestruct
	pop hl
	ret

By hit9918

Prophet (2868)

hit9918's picture

08-04-2018, 16:32

yes I was putting the vtable in every object. not for concept reasons but because slow z80

but look

        ld ix,0xc240 : call turtle_move
        ld ix,0xc120 : call mushroom_move
        ld ix,0xc080 : call turtle_move
        ld ix,0xc140 : call turtle_explode
        ret

I dont mean this coded, I mean things added to a code array by constructors.
it is pairs of objectpointer, functionpointer. that's the concept. for z80 reasons it is a code array

one turtle gets called move, but the other turtle gets called explode. it is more flexible than the usual OO!
and it is fast as hell

By hit9918

Prophet (2868)

hit9918's picture

08-04-2018, 17:02

things would be used in this style

turtle_construct:
                ...
                ;ix = object
                ld iy,actionarray       ;descriptor of the code array
                ld de,turtle_move       ;the function to call
                call addactor           ;add it to the code array

a pair of object pointer and function pointer, is there an OO scene name for it? I call it actor.

By Grauw

Ascended (8508)

Grauw's picture

08-04-2018, 19:01

It’s called a bound function. You can not only bind “this”, but any number of arguments.

http://en.cppreference.com/w/cpp/utility/functional/bind
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Closures are syntactic sugar for the same thing.

Page 3/4
1 | 2 | | 4