TL;DR;
How costly is it to put things on the stack with the Z80?
Very costly as there are no neat instructions to access stack at all, only 8 bit instructions to access indexed memory and only accessing the stack pointer allone is a already mess.
When going into details the 6502 almost looks good in comparsion. For both a seperate parameter stack may be a better solution.
The Long Read
(Caveat: I did look up the timing for the instructions here, but I didn't look up the deatails, so there might be cases and side effects I missed out)
I assume your question is about accessing data on the stack, not push or pop operations (*1). Looking at the basic load instruction and its timing should give a good overview how indexed memory access can be done:
Loading from memory
LD A,(nn) 13
LD HL,(nn) 16
LD rr,(nn) 20
LD r,(rr) 07
LD r,(ix+n) 19
Storing to memory
LD (nn),A 13
LD (nn),HL 16
LD (nn),rr 20
LD (rr),r 07
LD (ix+n),r 19
Several issues can be noted:
- There are no instruction to load or store 16 bit values (pointers) from an address pointed to by a register pair
- There is no way to load an index register from an indexed location, it needs to be loaded bytewise.
- Using the index registers is extremely slow (*2)
- Indexed access by register pairs don't allow the use of an offset.
Additional 'quirks' in the instruction set are degrading the use of index registers in general, which influences the use to access stack based data a lot:
- There is no way to just move SP to any other register
- Accessing SP can only be done by adding it to
HL
or IX
/IY
.
- There is no way to copy register pairs then pushing/poping them or use two 8 bit register moves
- There is no way to combine
HL
and IX
or IY
in one instruction (*3)
- There is no way to include
IX
and IY
in one instruction (*3)
- There is no way to load
IXH
(IYH
) addressed by IX
(IY
) (*3)
- There is no way to load
H
or L
from IXH
/IXL
or vice versa. (*3)
It's further important to keep the stack structure in mind:
- The only instruction directly accessing the stack are
PUSH
and POP
- The stack is 16 bit 'wide'
- Only register pairs or index registers can be pushed/poped (*1)
So loading a 16 bit value (pointer) from stack into DE
(*4), for further addressing, requires:
- Clearing one of the index registers (4B/14C)
- Adding SP to it (2B/15C)
- Loading
D
and E
with bytewise loads (3B/19C each)
That adds up to 6B/19C setup and 6B/38C for each pointer thereafter.
Loading DE
by using HL
requires.
- Moving the offest into
HL
(3B/10C)
- Adding
SP
to HL
(1B/11C)
- Loading the first byte (1B/7C),
- Incrementing
HL
(1B/6C)
- Loading the second (1B/7C)
Now we need 4B/21C for setup and 3B/20C to load the pointer.
Not using the index registers is already on the first word faster (-16C) and more compact (-4B) than doing so. And it gets worse when more parameters need to be accessed.
As usual, YMMV depending on the tast, just not much, as the underlaying problem is the way the index registers have been implemented. A great idea at first sight, but (more often than not) degrading in real world applications.
Interesting sidenote:
The undocumented 8085 instructions would have been way more useful here. Most notably LDSI
which in just 10 clocks loads DE
with SP
and adds an 8 bit constant.
*1 - Push register pair is 10 clocks, poping it is 11, while the same operation for index registers takes an additional 4 clocks (to read the prefix)
*2 - They all have at least one prefix byte accessed in 4 cycles, plus a displaccement byte to be read (if needed), adding 4 more.
*3 - IX
and IY
related opcodes are implemented by overriding HL
in an instruction via a prefix byte into IX
or IY
. Thus there are no opcodes that can include both
*4 - It has to be a register pair, as none of the index registers can be loaded using the other - and using itself can't work due the bytewise nature.