britlion wrote:Boriel, did you notice this one?
Even with the latest stack fixed version, I'm not understanding well how to do standard function calls with parameters. Where do I find my parameters? On the stack, sure...
This is an *advanced*
Glad to see you are willing to know it.
When I do
function thing (a as uByte, b as uByte)
I can get the computer to stop, and have a look at the stack.
It seems to consist of:
nn ; something
nn ; return address
The above stack frame trace is not ok. 8bit parameters (Byte, UByte) are pushed into the stack as 16bits, since Z80 stack push/pops 16bits. The Z80 stack is rather slow, and saving 1 bit on every push/pop would have had even more overhead. Since A register is mostly used with 8 bit values, and Z80 has the instructions PUSH AF and POP AF, they're used.
PUSH AF and POP AF push 16 bits, storing the accumulator (A register) into the high byte
of the value pushed into the stack. This means 8bit parameters takes 16bits, and only the high byte is used. So the above stack trace should look like:
; aa => byte of A parameter, bb = > byte of B parameter
; XX => ignored
aaXX ; A parameter
bbXX ; B parameter
nnnn ; return address
britlion wrote:What is that other 16 bit value on the stack? How should I be handling parameters? StdCall isn't documented in the wiki (and I documented fastcall, through reverse engineering!)
I'm rather busy at this moment
I will later document it.
Believe it or not I was thinking of writing an ebook!
For now, the stdCall scheme I use is the CALLEE scheme. In this calling convention, the called function will take charge of popping the function parameters out of the stack upon return. It's faster than C CALLER-scheme, and take less memory. The disadvantage of this scheme, on the other hand, is it does not allow a dynamic number of parameters, but this inconvenient can be circumvented.
The stdCall convention is as follows:
- The caller push the parameters in the stack in *reverse order*. Thus, funct(a, b), will push B, A (in that order) into the stack.
- 8-bit parameters are pushed as 16bit ones, in the high byte => NNXX (XX ignored).
- After the parameters are pushed, the function is called (CALL). This push the RETURN address into the stack
- The Called routine push the current IX value (used as a stack frame pointer) into the stack
- The Called routine now does LD IX, SP => IX is updated to the current stack.
- The Called routine now pushes local variables into the stack in the same order they are declared. So Dim a, b => Push A, B.
Now we can use (IX+n) for local variables and (IX-n-4) for parameters.
The "4" number comes from the 4 bytes (Return Address + old IX values pushed before).
- On function exit, the function is in charge of clearing the stack. To restore it, we recover the old stack pointer with LD SP, IX.
This restores SP to the value it has on entering the function. If the function had no parameters, we can RET
- If the function has parameters, then there's a problem: The ret address is on top of the stack, then the parameters to be removed, and since we can't use the stack to temporary save HL (push hl), because we're operating on it, several tricks are used. For example, using alternative HL' register (exx), etc.
The last point is usually done with something like
exx -> Saves current HL, and uses HL'
pop de (return address)
ld hl, -(size of parameters)
add hl, sp ; sp = sp - param size
push de (ret address back into the stack
To save memory, this sequence is placed only once, so many functions jump to it on exit.
Registers are used on RETURN from functions (not subs), to send return values.
- A register (accumulator), used for 8 bit returned values (function of type Ubyte, byte)
- HL register (16 bits) used to return Integer, UInteger and Strings (pointers to Block of memory)
- HL DE (32 bits), used to return Ulong, Long, Fixed
- A DE HL used for Floating points.
So upon returning a value with size greater than 8 bits the HL register is used, and its value must be preserved during the exit sequence.
britlion wrote:It also means that if I pop my 8 bit value off the stack with POP BC - the 8 bit parameter ends up in B! ?
Yes. In fact, for 8 bit operations, I use A register and H, so pop HL.
I'm worried because following the template in the asm library:
Function test(a as uByte, b as uByte) as uInteger
POP HL ; return address
ex (sp),hl ; get operand <= Callee scheme
For this routine at least, left me in the rom, at the halt fall point, with interrupts disabled! A crash.
In order to get it to work, and put one parameter in A, and the other in DE, it looks like I have to do
POP BC ; something
POP HL ; return address
POP BC ; A
POP BC ; b
PUSH HL ; put return address back
Four pops and a push, and some register futzing; which doesn't seem to be ideally fast!
You can rewrite the above code, I think as:
ex (sp), hl
ld e, h
ld d, 0
But I don't know what are you doing with this asm. So not sure.