1

I am getting acquainted to making C/C++ talk to ASM functions. For this, I started by understanding how the registers are used for arguments.

I have 2 routines that will just multiply 2 64bit floats. One of them returns the result, the other one saves the result in the first argument, that is being passed by reference. The first case was easy enough and it worked without issues, but the second case I couldn't make it work until I made g++ generate the assembly code to see how it should be done. The assembly and c++ host code go in their own files:

section .text
global Mul_Double_ASM
global Mul_Double_ASM_2

Mul_Double_ASM:
    mulsd   xmm0,   xmm1
    ret

Mul_Double_ASM_2:
    mulsd   xmm0,   [rdi]
    movsd   [rdi],  xmm0

The C++ code is simply:

#include <iostream>

using namespace std;

extern "C" double Mul_Double_ASM(double val1, double val2);
extern "C" void Mul_Double_ASM_2(double &val1, double val2);

int main(void)
    {
    double  val_1,
            val_2;

    cin >> val_1;
    cin >> val_2;

    cout << std::fixed << Mul_Double_ASM(val_1, val_2) << endl;

    Mul_Double_ASM_2(val_1, val_2);
    cout << std::fixed << val_1 << endl;

    return 0;
    }

It reads 2 user-provided numbers and multiply them with the assembly functions. Both codes work now, but the way I was attempting Mul_Double_ASM_2 was:

mulsd   xmm0,   xmm1

No ret at the end, and when I printed the value of val_1 it had the original value. Then I learned that the first argument is stored in the register rdi and so made the adjustment on my code.

So my question is: I could multiply xmm0 and xmm1 directly in the first function and never used rdi, but when the function is pass-by-reference then I needed to use rdi. Can you explain why using xmm0 and xmm1 works in the first case and not in the second?

JayY
  • 109
  • 10
  • 8
    Look up calling conventions for your platform / compiler. The short of it is that floats are passed in the `xmm` registers, but non-floats are passed in regular registers; references are treated as pointers (non-floats). – Justin Sep 01 '19 at 18:04
  • @Justin , I happen to have a page with calling conventions for the x64 with nasm (Linux) opened but didn't really spot anything saying what you explained: references are treated as pointers. It only mentions "xmm0 is first, xmm1 is second...". If you want to make this an answer, I gladly accept it. – JayY Sep 01 '19 at 18:55

1 Answers1

2

The calling convention used by GCC—the System V AMD64 ABI*—places floating point values in the XMM registers, but non-floats in the general purpose registers. References are passed as pointers (non-floats) in the assembly (see How are references implemented internally?).

This means that the two by-value doubles are passed to the function in xmm0 and xmm1, whereas the by-reference double's address is passed in rdi with the by-value double passed in xmm0.

Different calling conventions may vary, but register-based calling conventions commonly pass floating point values in the XMM registers rather than the general purpose registers.

* I couldn't find a guarantee that GCC uses this ABI, but it looks to be its default ABI.

Justin
  • 24,288
  • 12
  • 92
  • 142
  • GCC defaults to using the standard calling convention for the target system. e.g. MinGW GCC targets Windows x64 (even if cross-compiling from Linux), and GCC targeting Linux uses x86-64 System V. So yes, passing the first pointer/integer arg in RDI is a clear sign that it's using x86-64 SysV. – Peter Cordes Aug 13 '21 at 00:50