The ABI states that parameter values are classified according to a specific algorithm. Relevant here is:
If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS.
Each field of an object is classified recursively so that always two fields are considered. The resulting class is calculated according to the classes of the fields in the eightbyte:
In this case, each of the fields (for either a tuple or a pair) are of type uint64_t
and so occupy an entire "eightbyte". The "two fields" to be considered in each eightbyte, then, are the "NO_CLASS" (as per 3) and the uint64_t
field, which is classified as INTEGER.
There is also, related to parameter passing:
If a C++ object has either a non-trivial copy constructor or a non-trivial destructor, it is passed by invisible reference (the object is replaced in the parameter list by a pointer that has class INTEGER)
An object that doesn't meet those requirements must have an address, and therefore needs to be in memory, which is why the above requirement exists. The same is true for return values, though this seems to be an omitted in the specification (probably by accident).
Finally, there is:
(c) If the size of the aggregate exceeds two eightbytes and the first eight-byte isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument
is passed in memory.
That doesn't apply here, obviously; the size of the aggregate is exactly two eightbytes.
On returning of values, the text says:
- Classify the return type with the classification algorithm
Which means, as per above, that the tuple should be classifed as INTEGER. Then:
- If the class is INTEGER, the next available register of the sequence
%rax, %rdx is used.
This is quite clear.
The only still-open question is whether the types are non-trivially-copy-constructible/destructible. As mentioned above, values of such type cannot be passed or returned in registers, even though the specification does not seem to recognize the problem for return values. However, we can easily show that the tuple and pair are both trivially-copy-constructible and trivially-destructible, using the following program:
Test program:
#include <utility>
#include <cstdint>
#include <tuple>
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
cout << "pair is trivial? : " << is_trivial<pair<uint64_t, uint64_t> >::value << endl;
cout << "pair is trivially_copy_constructible? : " << is_trivially_copy_constructible<pair<uint64_t, uint64_t> >::value << endl;
cout << "pair is standard_layout? : " << is_standard_layout<pair<uint64_t, uint64_t> >::value << endl;
cout << "pair is pod? : " << is_pod<pair<uint64_t, uint64_t> >::value << endl;
cout << "pair is trivially_destructable? : " << is_trivially_destructible<pair<uint64_t, uint64_t> >::value << endl;
cout << "pair is trivially_move_constructible? : " << is_trivially_move_constructible<pair<uint64_t, uint64_t> >::value << endl;
cout << "tuple is trivial? : " << is_trivial<tuple<uint64_t, uint64_t> >::value << endl;
cout << "tuple is trivially_copy_constructible? : " << is_trivially_copy_constructible<tuple<uint64_t, uint64_t> >::value << endl;
cout << "tuple is standard_layout? : " << is_standard_layout<tuple<uint64_t, uint64_t> >::value << endl;
cout << "tuple is pod? : " << is_pod<tuple<uint64_t, uint64_t> >::value << endl;
cout << "tuple is trivially_destructable? : " << is_trivially_destructible<tuple<uint64_t, uint64_t> >::value << endl;
cout << "tuple is trivially_move_constructible? : " << is_trivially_move_constructible<tuple<uint64_t, uint64_t> >::value << endl;
return 0;
}
Output when compiled with GCC or Clang:
pair is trivial? : 0
pair is trivially_copy_constructible? : 1
pair is standard_layout? : 1
pair is pod? : 0
pair is trivially_destructable? : 1
pair is trivially_move_constructible? : 1
tuple is trivial? : 0
tuple is trivially_copy_constructible? : 1
tuple is standard_layout? : 0
tuple is pod? : 0
tuple is trivially_destructable? : 1
tuple is trivially_move_constructible? : 0
This implies that GCC is getting it wrong. The return value should be passed in %rax,%rdx.
(The main noticable differences between the types is that pair
is standard layout and is trivially move-constructible whereas tuple
is not, so it's possible that GCC is always returning non-trivially-move-constructible values via a pointer, for example).
movl
32 bit andmovq
64-bit !? – IdealityUINT32_MAX
, the upper 32-bit of the register are implicitly set to zero. – Homicidemovl
instruction clears the rest of the bits in the 64-bit destination register. Its just that the instruction encoding is 1 byte shorter in than the equivalent usingmovq
. – Sansburymovl $imm32, %r32
encoding is the 10 bytemovabs $imm64, %r64
(REX + opcode + 8byte immediate). The sign-extendingmovq $imm32, %r/m64
form is 7 bytes: It needs a mod/rm byte instead of encoding the dest register into the opcode. (So it can store to memory, likemovq $2, (%rdi)
is doing) – Stationmaster