Search code examples
cassemblyx86-64abisysv

Do C compilers guarantee two eightbyte field structs will be passed as INTEGER on SysV x64?


Specifically in the context of the SysV x86-64 ABI

If I have a struct with only two fields, such as:

typedef struct {
    void *foo;
    void *bar;
} foobar_t;

And I pass it to a function with a definition like so:

foobar_t example_function(foobar_t example_param);

The ABI seems to say that each eightbyte field should be passed as INTEGER to the function, therefore rdi == foo and rsi == bar. Similarly, when returning we should be able to use rax and rdx, since we don't need a memory pointer in rdi. If example_function is trivially defined as:

foobar_t example_function(foobar_t example_param) {
    return example_param;
}

A valid assembly implementation, ignoring prologue and epilogue, would be:

example_function:
   mov rax, rdi
   mov rdx, rsi
   ret

Conceivably, a mentally-deficient compiler could fill the struct with NO_CLASS padding and make that assembly invalid somehow. I'm wondering if it's written down anywhere that a struct with only two eightbyte fields must be handled this way.

The larger context to my question is that I'm writing a simple C11 task switcher for my own edification. I'm basing it largely on boost.context and this is exactly how boost passes two-field structs around. I want to know if it's kosher under all circumstances or if boost is cheating a little.


Solution

  • The ABI seems to say that each eightbyte field should be passed as INTEGER to the function, therefore rdi == foo and rsi == bar.

    Agreed, for "global" functions accessible from multiple compilation units, the argument structure is broken up into to eightbyte pieces, the first completely filled by foo, and the second completely filled by bar. These are classified as INTEGER, and therefore passed in %rdi and %rsi, respectively.

    Similarly, when returning we should be able to use rax and rdx, since we don't need a memory pointer in rdi.

    I don't follow your point about %rdi, but I agree that the members of the return value are returned in %rax and %rdx.

    A valid assembly implementation, ignoring prologue and epilogue, would be: [...]

    Agreed.

    Conceivably, a mentally-deficient compiler could fill the struct with NO_CLASS padding and make that assembly invalid somehow. I'm wondering if it's written down anywhere that a struct with only two eightbyte fields must be handled this way.

    A compiler that produces code conforming to the SysV x86-64 ABI will use the registers already discussed for passing the argument and returning the return value. Such a compiler is of course not obligated to implement the function body exactly as you describe, but I'm not seeing your concern. Yes, these details are written down. Although the specific case you present is not explicitly described in the ABI specification you linked, all of the behavior discussed above follows from that specification. That's the point of it.

    A compiler that produces code (for a global function) that behaves differently is not mentally-deficient, it is non-conforming.

    The larger context to my question is that I'm writing a simple C11 task switcher for my own edification. I'm basing it largely on boost.context and this is exactly how boost passes two-field structs around. I want to know if it's kosher under all circumstances or if boost is cheating a little.

    It would take me more analysis than I'm prepared to expend to determine exactly what Boost is doing in the code you point to. Note that it is not what you present in your example_function. But it is reasonable to suppose that Boost is at least attempting to implement its function calls according to the ABI.