FAQ  •  Register  •  Login

Function calls

<<

britlion

Posts: 762

Joined: Mon Apr 27, 2009 7:26 pm

Location: Slough, Berkshire, UK

Post Wed Feb 17, 2010 1:15 pm

Function calls

When doing assembler based function calls, there's something that confuses me about the stack.
  Code:
FUNCTION thing (num1 as uByte, num2 as uByte) as uByte
asm
DI
HALT
end asm
END FUNCTION


When the virtual computer crashes, you can look at the registers and the stack and find out what it's doing.

The stack seems to have
uinteger <something>
uinteger <return address>
uinteger <44,num1>
uinteger <44,num2>

A is set to num1

First question: What's the <something> ? I end up popping it off the stack and dumping it. This worries me.
Second question: If I can trust A to be num1 already, why do I have to go through num1 to get to num2?
Third question: Why the 44's strapped to each byte parameter?

So right now I end up:
  Code:
POP BC  ; throw this away
POP HL  ; return address
POP AF  ; num 1 -> A
POP DE  ; num2 -> D.

And since that's less than helpful:
LD E,D
LD D,0

To make DE the value of num2.


Question 1 worries me most. What IS that extra value on the stack?

Is there a better way of handling parameters - with IX+offset, say?
Does the compiler set it to clean up the stack afterwards, and I shouldn't POP it at all?

Sorry to whine, but this isn't documented, and I'm trying to reverse engineer it! I've got the hang of fastcall, but soemtimes I want more than one parameter.
<<

britlion

Posts: 762

Joined: Mon Apr 27, 2009 7:26 pm

Location: Slough, Berkshire, UK

Post Wed Feb 24, 2010 11:49 pm

Re: Function calls

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...

When I do
  Code:
function thing (a as uByte, b as uByte)
asm
DI
HALT
end asm
end function

I can get the computer to stop, and have a look at the stack.

It seems to consist of:

nn
nn ; something
nn
nn ; return address
a
44h
b
44h


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!)

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! ?

I'm worried because following the template in the asm library:

  Code:
Function test(a as uByte, b as uByte) as uInteger
asm
    POP HL ; return address
    ex (sp),hl ; get operand
   


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
  Code:
    POP BC ; something
    POP HL ; return address
    POP BC ; A
    LD A,B
    POP BC ; b
    LD D,0
    LD E,B
    PUSH HL ; put return address back

Four pops and a push, and some register futzing; which doesn't seem to be ideally fast!
<<

boriel

Site Admin

Posts: 1461

Joined: Wed Nov 01, 2006 6:18 pm

Location: Santa Cruz de Tenerife, Spain

Post Thu Feb 25, 2010 8:47 am

Re: Function calls

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* info request :!: :D
Glad to see you are willing to know it.

britlion wrote:When I do
  Code:
function thing (a as uByte, b as uByte)
asm
DI
HALT
end asm
end function

I can get the computer to stop, and have a look at the stack.

It seems to consist of:

  Code:
nn
nn  ; something
nn
nn  ; return address
a
44h
b
44h


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:
  Code:
; 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! :D

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:
  1. 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.
  2. 8-bit parameters are pushed as 16bit ones, in the high byte => NNXX (XX ignored).
  3. After the parameters are pushed, the function is called (CALL). This push the RETURN address into the stack
  4. The Called routine push the current IX value (used as a stack frame pointer) into the stack
  5. The Called routine now does LD IX, SP => IX is updated to the current stack.
  6. 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).
  7. 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
  8. 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
  Code:
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
exx
ret

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.

britlion wrote:I'm worried because following the template in the asm library:

  Code:
Function test(a as uByte, b as uByte) as uInteger
asm
    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
  Code:
    POP BC ; something
    POP HL ; return address
    POP BC ; A
    LD A,B
    POP BC ; b
    LD D,0
    LD E,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:
  Code:
pop bc
pop hl
pop af
ex (sp), hl
ld e, h
ld d, 0

But I don't know what are you doing with this asm. So not sure.
<<

britlion

Posts: 762

Joined: Mon Apr 27, 2009 7:26 pm

Location: Slough, Berkshire, UK

Post Sat Feb 27, 2010 4:53 am

Re: Function calls

Thankyou for taking the time to explain this.

It will be incredibly useful! I've been spending a lot of time dabbling with adding hand-coded asm functions and routines (because ZX Basic makes it so easy to do bit of assembler with the main program!)

Return to ZX Basic Compiler

Who is online

Users browsing this forum: No registered users and 2 guests

cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group.
Designed by Vjacheslav Trushkin for Free Forums/DivisionCore.

phpBB SEO