how gcc handle the stack to pass args to function

Ask anything your want about Megadrive/Genesis programming.

Moderator: BigEvilCorporation

Post Reply
Pascal
Very interested
Posts: 200
Joined: Wed Nov 29, 2006 11:29 am
Location: Belgium
Contact:

how gcc handle the stack to pass args to function

Post by Pascal » Mon Jul 16, 2007 12:43 pm

Hello all,

i've start to play with gcc, being a snasm guy , i was wondering how gcc handle the stack for passing arguments to pure asm function (my aim is to mix asm & C) ?

I'm looking in fdarkangel lib code , but it still not clear to me

Example:

Code: Select all

.section .text
.global gen_set_vscroll
/* void gen_set_vscroll(u16 sa, u16 sb) */
gen_set_vscroll:
	move.l  #0x40000010, PORT_VDP_CNTL
	move.l  #PORT_VDP_DATA, %a0
	move.w  6(%sp),  (%a0)
	move.w  10(%sp), (%a0)
	rts
why 6(sp) for parameters Sa ? and 10(sp) for sb ?

if i look to FrogFeast source it seems handled differently:

Code: Select all

* void LoadPalette(WORD *Source, DWORD Dest, DWORD Length);
* A0 = ptr to NeoGeo palette
* A1 = ptr to our palette
* D0 = palette size
LoadPalette:
	.set	_ARGS, 4
	move.l	_ARGS(a7), a1
	move.l	_ARGS+4(a7), a0
	move.l	_ARGS+8(a7), d0

	* Backup data registers
	movem.l	d0/a0-a1, -(sp)
here's seems more logic, 4 bytes shift at the beginning (return address most probably). Why this difference between the 2 methods ??

Also how can i ask GCC to align my asm function on a 16bits bundary ? (like cnop 0,2)

thanks in advance

8bitwizard
Very interested
Posts: 159
Joined: Sat Feb 24, 2007 11:35 pm
Location: San Antonio, TX

Post by 8bitwizard » Mon Jul 16, 2007 2:00 pm

It looks like the compiler is reserving 4 bytes on the stack for each parameter, even when it's only using the low word.

cdoty
Very interested
Posts: 117
Joined: Wed Nov 29, 2006 2:54 pm
Location: Houston, TX
Contact:

Post by cdoty » Thu Sep 20, 2007 3:31 am

Yep. It's 4 bytes regardless of parameter size.

To align the code use:

.align x

where x is the byte to align to. For example, .align 4 creates DWORD alignment,

Chilly Willy
Very interested
Posts: 2984
Joined: Fri Aug 17, 2007 9:33 pm

Post by Chilly Willy » Thu Sep 20, 2007 5:27 am

8bitwizard wrote:It looks like the compiler is reserving 4 bytes on the stack for each parameter, even when it's only using the low word.
The M68K ABI specifies that all parameters be promoted to integers and pushed right to left. D0-D1/A0-A1 are scratch, and all other regs (other than A7 of course) must be saved. The M68K ABI was only ever published in a BOOK that has long since been out of print, but some time spent googling yielded the answer.
4.2 Interfacing C and assembly language functions

4.2.1 68k
This section describes the function call interface used by the gnu
compiler on the Motorola M68k family of processors.

4.2.1.1 Calling convention
Functions are called with the JSR (jump to subroutine) instruction. If
the function returns a value, it will be in register D0 when the
function returns. If the function requires any parameters, they are
pushed onto the stack with the rightmost parameter first. All 8- and
16-bit parameters are promoted to integers before being pushed onto
the stack; the default size for integers is 32 bits, or 16 bits if the
-mshort option is passed on the compiler command line.

The compiler maintains a stack frame pointer in register a6. The frame
pointer is used as a base register to allow access to both function
parameters and local variables on the stack using an indexed
addressing mode. C functions save the frame pointer on the stack when
they are called, and restore it before they return. In addition, they
save any processor registers that they modify except for d0, d1, a0,
and a1; these are considered "scratch" registers which may be used
by functions without preserving their contents.

Here's an example of calling a C function from assembly language.
Function abc has the following prototype:

int abc (int a, char *b);

To call this function, an assembly language routine would push b onto
the stack, then a. It would then call the function by executing JSR
ABC. Upon returning, register d0 would contain the return value of the
function.

4.2.1.2 Register Usage
Registers D0, D1, A0, and A1 are scratch registers and are not saved
and restored when calling other functions. All other registers that
are used by a function must be saved on the stack before being
modified, and restored from the stack before the function returns. If
the target implements hardware floating point, either internally (such
as the 68040) or through a floating-point coprocessor, then FP0 and
FP1 are also scratch registers. All other floating-point registers
must be saved and restored by the function if used.

4.2.1.3 Stack cleanup
When a function returns control to the function that called it, the
stack space consumed by the function's parameters needs to be
deallocated (i.e. the stack pointer must be incremented back to its
value before the parameters were pushed). There are two ways of doing
this, depending upon whether or not the compiler option -mrtd was
used to compile the code.

The default strategy (that is, when -mrtd is not used) requires the
calling function to deallocate the stack space used by function
parameters. In our abc example earlier, this can be accomplished with
the following instruction (after the JSR ABC):

addq.l #8,%sp

The alternative approach, when -mrtd is used, requires the called
function to clean up the stack; in this case the assembly language
function need not do anything, since the C function that it called
would have ended with the RTD #n instruction, which removes the
parameter space as part of its execution.

The problem for assembly language programmers is, how do you know when
you write the code which calling convention is being used? It would
certainly be very inconvenient to have to edit every function call if
the calling convention were changed. The answer lies in gcc's ability
to preprocess assembly language programs before passing them to the
assembler. The 68k version of gcc defines the preprocessor symbol
__MRTD__ if code is compiled or assembled using the -mrtd command-line
option. By testing for the presence of this symbol using the #ifdef
preprocessor construct, you can put code in your program to handle
both cases and conditionally assemble the correct version.

In order to preprocess your assembly code, you must name the source
file with the '.S' extension (i.e. 'program.S' rather than
'program.s'). In addition, you must use gcc to assemble the program
rather than invoking the assembler directly.

Several examples of this type of conditional assembly may be found in
crt0.S, the source file of the C startup module.

4.2.1.4 16-bit ints
In addition to the RTD calling convention described in the previous
section, the other issue of which assembly language programmers need
to be aware concerns the size of int function parameters. Normally,
Gnu CC defaults to 32-bit ints, and all function parameters are
promoted to int size before being pushed on the stack. However, the
68k compiler has a command-line option, -mshort, to set the size of
int variables and function parameters to be 16 bits wide. If this
compiler option is in effect, then the amount of stack space allocated
by function parameters will be different from the default case. In
addition, function parameters on the stack will be located at a
different offset from the stack pointer depending on whether or not
ints are 16 or 32 bits wide.

So how does the assembly language programmer know which case is in
effect? This problem is solved in a fashion similar to the RTD calling
sequence described earlier. By running assembly language source files
through the C preprocessor, you will be able to test the value of the
macro __INT_MAX__. The -mshort compiler option will cause __INT_MAX__
to have the value 32767. Any other value indicates that ints are 32
bits wide. By testing this value using #if/#else, you can write
assembly language programs that have code for both cases and
conditionally assemble the correct version.

Post Reply