Hi,
I'm writing a little Forth-ish stack VM in Z80 assembler for the CPC and I've hit a stupid, trivial snag. I'd like to use a jumptable of calls based on the opcode but I couldn't figure out a nice way of doing it.
What I'm currently doing just to get around it (from memory):
PUSH IX
PUSH BC
LD IX, jumptable
LD BC, 0
LD C, A ; A has the jumpcode op in it
ADD IX, BC
ADD IX, BC ; jumptable is two bytes per entry
LD C, (IX+0)
LD B, (IX+1)
LD IX, call_offset
LD (IX+1), C
LD (IX+2), B
POP BC
POP IX
call_offset: CALL &0
RET
Now - this is fine for RAM code but I'd like to eventually put this VM in a ROM, rather than in RAM. I can always just use scratch RAM for that rather than self-modifying in-line code.
Does anyone have any suggestions on better (tighter, faster) ways of doing this? Besides the obvious "two 16-bit ADDs? are you crazy?" :-)
Thanks!
If you just want to eliminate the self modifying code, you can do the equivalent of CALL IX with:
CALL call_ix
RET
call_ix: JP (IX)
EDIT: Although, having re-read it again I think you need CALL BC, which should be something like:
CALL call_bc
RET
call_bc: PUSH BC
RET
Quote from: erikarn on 04:20, 06 September 10
Hi,
I'm writing a little Forth-ish stack VM in Z80 assembler for the CPC and I've hit a stupid, trivial snag. I'd like to use a jumptable of calls based on the opcode but I couldn't figure out a nice way of doing it.
What I'm currently doing just to get around it (from memory):
PUSH IX
PUSH BC
LD IX, jumptable
LD BC, 0
LD C, A ; A has the jumpcode op in it
ADD IX, BC
ADD IX, BC ; jumptable is two bytes per entry
LD C, (IX+0)
LD B, (IX+1)
LD IX, call_offset
LD (IX+1), C
LD (IX+2), B
POP BC
POP IX
call_offset: CALL &0
RET
Now - this is fine for RAM code but I'd like to eventually put this VM in a ROM, rather than in RAM. I can always just use scratch RAM for that rather than self-modifying in-line code.
Does anyone have any suggestions on better (tighter, faster) ways of doing this? Besides the obvious "two 16-bit ADDs? are you crazy?" :-)
Thanks!
You can either have the addresses for each opcode in the table:
e.g.
defw opcode0
defw opcode1
and have code like this:
ld l,a
ld h,0
add hl,hl
ld de,jumptable
add hl,de
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
jp (hl)
or you could have the jumps themselves
jp opcode0
nop
jp opcode1
nop
etc
and have this
ld l,a
ld h,0
add hl,hl
add hl,hl
ld de,opcode_table
add hl,de
jp (hl)
all of these work without self modifying code and may be quicker...
(the nops are added between the jp, to make them each on a 4-byte boundary, and making the code to jump to them a bit smaller and faster, but this option means more ram used for the jumptable)
if there are less opcodes than 255 you can do other tricks to make it faster.
Just thinking about doing it with registers preserved, a slight variation of the one given by arnoldemu:
push hl
push de
ld l,a
ld h,0
add hl,hl
ld de,jumptable
add hl,de
ld a,(hl)
inc hl
ld h,(hl)
ld l,a
pop de
ex (sp),hl
ret
might work?