6 ms·
Unfortunately in many cases this will cause all of the parameters to be passed via the stack rather than in registers. It’s a meaningful penalty for the kind of
by jleahy 6y ago
Unfortunately in many cases this will cause all of the parameters to be passed via the stack rather than in registers. It’s a meaningful penalty for the kind of people who would willingly select c++ as a language today (rather than, say, python or java).
- ducaale 6y agoWouldn't be possible for the compiler to optimize it away?
- flohofwoe 6y agoOnly if the function is inlined, at least it seems that way from experimenting on godbolt.org.
- nikki93 6y agohttps://godbolt.org/z/11931W https://godbolt.org/z/11931W Here's an example where it happens even when not inlining.
- flohofwoe 6y agoI guess that's because the struct size is below 16 bytes (see my sister comment). Adding two more ints to the struct places the entire content on the stack.
- bitwalker 6y agoI'd expect scalar replacement of aggregates (SROA) would eliminate the use of a struct like this in some cases, but there are definitely limits, especially if not compiling with LTO enabled, since optimizations across compilation units will be limited. Honestly doesn't seem worth the cost.
- nikki93 6y agoUsing registers to pass structs is an ABI thing and doesn't need LTO, it happens during compilation (see godbolt.org links on the rest of this thread).
- flohofwoe 6y agoFrom my experience with C99: if the struct size is 16 bytes or less, the values will be packed into registers. But true as soon as the struct grows bigger, the entire content will be passed on the stack. IMHO this sort of named arguments "easter egg" is fine for big "option bag structs" passed to functions that are not performance critical (or for small struct <= 16 bytes). For other cases, regular args are better. OTH it seems like passing normal args also spills over into the stack very soon (only 4 registers used?): https://www.godbolt.org/z/Mz9MTG https://www.godbolt.org/z/Mz9MTG The main difference seems to be that normals arguments "spill over", while passing structs by value puts everything on the stack once the threshold size is passed.
- archgoon 6y agoSix arguments use registers (and rax for return). The first two in your example are in the lea instruction. Using rdi, rsi, rdx, rcx, r8, r9 and then the stack is the calling convention used on x64 linux. 32-bit (-m32 in godbolt) linux would use the stack exclusively.
- account42 6y ago> Using rdi, rsi, rdx, rcx, r8, r9 and then the stack is the calling convention used on x64 linux It's more complicated than that - floats/doubles and structs of them are put in SSE registers instead. Mixed structs are packed into integer registers (!) if they are small enough. And with C++ classes with non-trivial destructor/etc. are always passed on the stack.
- nikki93 6y agoSix of the parameters use registers here. the `lea` is putting the sum of the first two into `eax`, then the rest accumulate more adds into `eax`. Only the last 3 parameters are reading from stack memory (the `dword ptr [*]` stuff). In any case you don't get to actually see the passing at the callsite because the result `45` is inlined into `main()`. :)
- flohofwoe 6y ago
- andi999 6y agoWhy?
- dahfizz 6y agoEverything that's not a struct that you would pass as a parameter (int, pointer, char, etc) all fit in a register and there are register reserved for function parameters in the ISA. Structs with more than one field don't fit into a register.
- gumby 6y agoYou can write a trivial template that generates a parameter pack which the compiler should be able to optimize at compile time. However in my experience there are human factors problems which I posted in another comment.
- asveikau 6y agoIn c++ it also seems likely that with this approach you could invoke some pretty expensive copy constructors if some of the parameters have those.
- nikki93 6y agoIf it's a prvalue there is no copying involved. Here's an example where one of the 'parameters' has an 'expensive' copy constructor but it isn't invoked at all: https://godbolt.org/z/94ehvx https://godbolt.org/z/94ehvx However, if you use an lvalue expression then yes, the copy constructor is invoked (in `bar` here), and you can avoid that by using an xvalue expression (in `baz`, using `std::move`): https://godbolt.org/z/MnKnWM https://godbolt.org/z/MnKnWM Basically it doesn't depend on being passed through a struct as much as on the parameter itself. You'd see the same behavior if the `C` struct was passed as a regular parameter.
- moonchild 6y agoPer the sysv abi, sec. 3.2.3, parameter passing: > If the size of an object is larger than eight eightbytes, or it contains unaligned fields, it has class MEMORY > [...] > If the size of the aggregate exceeds a single eightbyte, each is classified separately. Each eightbyte gets initialized to class NO_CLASS. So that only happens if the whole structure is bigger than 64 bytes. Otherwise everything gets passed in registers like normal.
- account42 6y agoOnly 16 bytes are passed in registers except for float types: > 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 argumentis passed in memory.