The MASM Forum

General => The Laboratory => Topic started by: bulk88 on November 13, 2014, 05:16:38 PM

Title: a vararg Win32 __stdcall function
Post by: bulk88 on November 13, 2014, 05:16:38 PM
There is a general thought that a vararg stdcall function can not be created. One reason is "ret" instruction only takes imm16, not a register. The other is, that supposedly (I can't find the actual line in C89 to support this) C doesn't require that all args passed into the function to be va_arg()ed. The callee can ignore excess args according to C. But dont we want to use __stdcall to avoid all the extra "add esp, *" instructions, or the fat "mov eax, dword ptr [esi+*]; mov dword ptr [esp+*], eax" instructions used in non-moving esp style functions instead of push? Well, we can. Here is a simple cdecl vararg function converted into a stdcall vararg function. To use it from C, you will need to cast it every time obviously and write C macros ADD1, ADD2, ADD3, ADD4, etc.

Before

unsigned int adder(unsigned int paramCount, ...) {
    va_list va;
    unsigned int ret = 0;
    unsigned int i;
    va_start(va,paramCount);
    for(i=0;i<paramCount;i++){
        ret += va_arg(va,unsigned int);
    }
    va_end(va);
    return ret;
}

after

_paramCount$ = 8 ; size = 4
_addersc PROC NEAR ; COMDAT

; 273  :     unsigned int i;
; 274  :     va_start(va,paramCount);
; 275  :     for(i=0;i<paramCount;i++){
; call DWORD PTR __imp__DebugBreak@0
mov edx, DWORD PTR _paramCount$[esp-4]
xor eax, eax
test edx, edx
jbe SHORT _end

; 271  :     va_list va;
; 272  :     unsigned int ret = 0;

lea ecx, DWORD PTR _paramCount$[esp-4]
redo:

; 276  :         ret += va_arg(va,unsigned int);

add ecx, 4
add eax, DWORD PTR [ecx]
dec edx
jne SHORT redo
_end:

; 277  :     }
; 278  :     va_end(va);
; 279  :     return ret;
; 280  : }
pop edx ; put ret addr in reg
add ecx, 4 ; wipe arg paramCount's space
mov esp, ecx ; put esp at end of incoming args
push edx ; put ret addr back in stack in new location
ret ; use ret instruction instead of jmp edx to avoid
        ;the .2 us delay (by my measurement) of a missed CPU return stack buffer
        ;prediction
_addersc ENDP
Title: Re: a vararg Win32 __stdcall function
Post by: dedndave on November 13, 2014, 11:05:28 PM
C calling convention allows this because the RET instruction does not "remove" args from the stack
instead, the caller is responsible for balancing the stack after the call
Title: Re: a vararg Win32 __stdcall function
Post by: jj2007 on November 14, 2014, 01:58:12 AM
Hi bulk,
Welcome to the Forum :icon14:
You might like this thread (http://www.masmforum.com/board/index.php?topic=14679.msg119125#msg119125).
Title: Re: a vararg Win32 __stdcall function
Post by: Gunther on November 14, 2014, 04:12:50 AM
Hi bulk88,

welcome to the forum and have a lot of fun.

Gunther
Title: Re: a vararg Win32 __stdcall function
Post by: Vortex on November 14, 2014, 07:12:20 AM
Not the best solution but the code below is working :

include     \masm32\include\masm32rt.inc
include     invoke.inc

.data

string      db 'Sum = %u',13,10,0

.data?

.code

start:

    _invoke CalcSum,1,3,5,7,9

    invoke  crt_printf,ADDR string,eax

    _invoke CalcSum,2,4,6

    invoke  crt_printf,ADDR string,eax

    invoke  ExitProcess,0


CalcSum PROC

    LOCAL   argcnt:DWORD

    mov     ecx,DWORD PTR [ebp+8]       ; get the number of parameters
                                        ; obtained by the _invoke macro
    mov     argcnt,ecx
                   
    xor     eax,eax
    lea     edx,[ebp+12]                ; get the address of the first
                                        ; argument
@@:
    add     eax,DWORD PTR [edx]
    add     edx,4
    dec     ecx
    jnz     @b

    mov     ecx,argcnt
    inc     ecx
    shl     ecx,2
    leave
    mov     edx,DWORD PTR [esp]         ; get the return address
    add     esp,ecx                     ; balance the stack
    mov     DWORD PTR [esp],edx
    retn

CalcSum ENDP

END start