Re:Let's Write Some x86-64
by userbinator on Friday May 02, 2014 @10:33AM
(#7685886)
The GP is not alone in thinking that the x86-64 ABI is "a total mishmash"; it feels to me like the designers were deliberately avoiding optimisation, when an ABI is really one thing where any optimisation wouldn't ever be "premature" - after all, it's something that's going to be used by millions if not more pieces of software, so little things really add up.
For example, it's not necessary that all the args passed in registers need be written back to the stack by va_start --- ignoring for the moment the complication of structures and SSE registers, a va_list could just contain one field, which would initially hold an index, the "current register number" where the desired argument is stored. Then va_arg (which is now implemented as a compiler primitive) could generate code that checks this index, and if the index is less than the maximum number of arguments that can be passed in registers, resolve to an access to the particular register (here's where a "register-indirect" addressing mode would be really useful, since it could just use that index). Otherwise that field becomes a pointer into the stack like the traditional implementation. In other words, depending on that index, reading a scalar va_arg turns into reading a register or reading from the stack.
It's not that hard to extend this to accommodate structures; and I don't see any limiting reason on why the ABI couldn't pass structures partially in registers and partially on the stack. We just have to define the rules that let us do so, as a structure is only a concept of grouping data together, an HLL construct. There's nothing that restricts a structure to being always a contiguous set of registers or memory locations. Having to "reassemble" a structure in memory is only necessary if its address is taken, and then only if operations other than accessing its members via that address are performed; otherwise its components can be operated on independently, regardless of whether they're in registers or memory. Since the compiler knows where va_arg is used with a structure type, it can decide whether it really needs to generate the code to reassemble the structure, or only a series of scalar accesses. (I touch on this point somewhat here too: https://news.ycombinator.com/item?id=7683823 )
Extending this to the SSE regs is simpler than for structures, since this is merely another bank of registers that arguments can be read from: another field in va_list to hold the SSE register index/pointer.
I find it a bit odd that the ABI would define an actual implementation of varargs rather than only where the arguments will be expected to be, since how they're actually accessed should be outside the scope of an ABI spec; now every compiler vendor is tempted to just copy this (IMHO sub-optimal) implementation instead of thinking about how it could be done, and possibly coming up with something better.
For example, it's not necessary that all the args passed in registers need be written back to the stack by va_start --- ignoring for the moment the complication of structures and SSE registers, a va_list could just contain one field, which would initially hold an index, the "current register number" where the desired argument is stored. Then va_arg (which is now implemented as a compiler primitive) could generate code that checks this index, and if the index is less than the maximum number of arguments that can be passed in registers, resolve to an access to the particular register (here's where a "register-indirect" addressing mode would be really useful, since it could just use that index). Otherwise that field becomes a pointer into the stack like the traditional implementation. In other words, depending on that index, reading a scalar va_arg turns into reading a register or reading from the stack.
It's not that hard to extend this to accommodate structures; and I don't see any limiting reason on why the ABI couldn't pass structures partially in registers and partially on the stack. We just have to define the rules that let us do so, as a structure is only a concept of grouping data together, an HLL construct. There's nothing that restricts a structure to being always a contiguous set of registers or memory locations. Having to "reassemble" a structure in memory is only necessary if its address is taken, and then only if operations other than accessing its members via that address are performed; otherwise its components can be operated on independently, regardless of whether they're in registers or memory. Since the compiler knows where va_arg is used with a structure type, it can decide whether it really needs to generate the code to reassemble the structure, or only a series of scalar accesses. (I touch on this point somewhat here too: https://news.ycombinator.com/item?id=7683823 )
Extending this to the SSE regs is simpler than for structures, since this is merely another bank of registers that arguments can be read from: another field in va_list to hold the SSE register index/pointer.
I find it a bit odd that the ABI would define an actual implementation of varargs rather than only where the arguments will be expected to be, since how they're actually accessed should be outside the scope of an ABI spec; now every compiler vendor is tempted to just copy this (IMHO sub-optimal) implementation instead of thinking about how it could be done, and possibly coming up with something better.