helloFromTheOtherSide
helloFromTheOtherSide

Reputation: 53

How to call a function through a function pointer passed as an argument?

How do I call passed function (*f2) in third argument of f1 function in assembly? Declaration looks like that:

extern float f1(int v1, float v2, float (*f2)(int v3, float v4));

I want to pass v1 to v3, v2 to v4 , call function f2, and return value

f1:
    push rbp           
    mov rbp, rsp

    mov rdx, rdi ; v1 to v3
    mov xmm1, xmm0 ; v2 to v4
    call ??? 
    mov xmm0, xmm1

    mov rsp, rbp       
    pop rbp    
ret

what do I put in place of the question marks?

Upvotes: 5

Views: 6725

Answers (2)

Cody Gray
Cody Gray

Reputation: 244732

There is no such thing as "Abi64". Since you tagged the question MASM, we can guess that you're using the Windows platform, and clearly the "64" means that this is 64-bit code, so that does narrow down the possibilities tremendously. However, there are still two common calling conventions for 64-bit code on Windows. One of them is __vectorcall and the other is the Microsoft x64 calling convention (the one that was originally invented to make all other calling conventions obsolete but…didn't).

Since the Microsoft x64 calling convention is the most common, and in this particular case, using __vectorcall wouldn't change anything, I'll assume it's the one that you're using. And then the code required becomes absolutely trivial. All you need to do is jump from f1 to f2, since the stack will be set up identically. f1's first two parameters are the two parameters that should be passed to f2, and the return value of f2 is the return value of f1. Therefore:

f1:
    rex_jmp  r8    ; third parameter (pointer to f2) is passed in r8

This is not only trivial to write, but it is the most optimal implementation for both size and speed.
You can even modify the v1 or v2 parameters beforehand if you want, e.g.:

f1:
    inc      ecx        ; increment v1 (passed in ecx)

    ; multiply v2 (xmm1) by v1 (ecx)
    movd     xmm0, ecx
    cvtdq2ps xmm0, xmm0
    mulss    xmm1, xmm0

    rex_jmp  r8    ; third parameter (pointer to f2) is passed in r8

In case you wanted to do something more complicated, here's how it would work:

f1:
    sub   rsp, 40     ; allocate the required space on the stack
    call  r8          ; call f2 through the pointer, passed in r8
    add   rsp, 40     ; clean up the stack
    ret

Note that you do not need the prologue/epilogue code that you've shown in the question, though it won't hurt anything if you choose to include it.

However, the shuffling of parameters that you were doing in the example code shown in the question is wrong! In the Microsoft x64 calling convention, the first up-to-four integer arguments are passed in registers, from left to right, in RCX, RDX, R8, and R9. All other integer arguments are passed on the stack. The first up-to-four floating-point values are also passed in registers, from left to right, in XMM0, XMM1, XMM2, and XMM3. The rest are passed on the stack, along with structs too large for registers.

The weird thing, though, is that the slots are "fixed", so only 4 total register args can be used, even when you have a mix of integer and fp args. Thus:

╔═══════════╦══════════════════════════╗
║           ║           TYPE           ║
║ PARAMETER ╠═════════╦════════════════╣
║           ║ Integer ║ Floating-Point ║
╠═══════════╬═════════╬════════════════╣
║ First     ║   RCX   ║      XMM0      ║
╠═══════════╬═════════╬════════════════╣
║ Second    ║   RDX   ║      XMM1      ║
╠═══════════╬═════════╬════════════════╣
║ Third     ║   R8    ║      XMM2      ║
╠═══════════╬═════════╬════════════════╣
║ Fourth    ║   R9    ║      XMM3      ║
╠═══════════╬═════════╩════════════════╣
║ (rest)    ║         on stack         ║
╚═══════════╩══════════════════════════╝

It doesn't matter that the second parameter is the first floating-point value that is being passed. It doesn't go in XMM0 because it's the first floating-point value, it goes in XMM1 because it's the second parameter and therefore in the second "slot". (This is different from the x86-64 System V ABI, where the first 6 integer args go in registers, whether or not there are FP args).

More detailed documentation on Windows parameter passing is available here, including examples.

Upvotes: 6

MGpro
MGpro

Reputation: 43

Assembly code differs based on the Micro controller in used.

Not exactly what you are looking for but following the assembly code generated by on Windows platform having intel core I7:-

C code:-

extern float f1(int v1, float v2, float (*f2)(int v3, float v4))
{
    float a = 10.0;
    int b = 12;

    f2(b, a);
    return a+ 12.5;
}

Assembly code:-

_f1:
 pushl  %ebp
 movl   %esp, %ebp
 subl   $40, %esp
 movl   LC0, %eax

 movl   %eax, -12(%ebp)
 movl   $12, -16(%ebp)
 movl   -12(%ebp), %eax
 movl   %eax, 4(%esp)
 movl   -16(%ebp), %eax
 movl   %eax, (%esp)
 movl   16(%ebp), %eax
 call   *%eax
 fstp   %st(0)
 flds   -12(%ebp)
 flds   LC1
 faddp  %st, %st(1)
 leave
ret

Hope this will help.

Upvotes: -2

Related Questions