5 ms·
In the nested function, the variable x is passed in edi, and the pointer to the nested stack frame is passed in r10. In the lambda, the variable x is passed in
by jcranmer 7d ago
In the nested function, the variable x is passed in edi, and the pointer to the nested stack frame is passed in r10. In the lambda, the variable x is passed in esi, and the 'this' pointer for the lambda is passed in rdi. The function-level ABIs end up being quite different.
- uecker 7d agoThanks, but now you should explain why you think this slight (and well understood) difference in calling convention is a rather important difference.
- tptacek 7d agoDoesn't this imply that the two functions have different semantics for capturing the environment?
- uecker 7d agoNo, why? It simply means that the arguments are in different registers.
- jcranmer 7d agoI can write the signature of the generated function of a C++ lambda as a C function. I cannot write the signature of a generated GCC nested function as a C function. If your argument is that ABI doesn't matter, then by all means, propose a patch to GCC to change the ABI and see if it gets accepted.
- uecker 7d agoI can not write the signature of a function that requires a static chain (which exist in many languages) in C, because we have not added such a feature to the language. But we could. And we should, because we now need a (type-unsafe) extension (__builtin_call_with_static_chain) to invoke such functions. I do not want to change the ABI for nested functions as it is a useful ABI and a cross-language standard. ABI obviously matters, but it is not a fundamental difference in implementation that makes nested functions fundamentally different to C++ lambdas. If your point is merely that a compiler translating nested functions to lambdas would need to adapt the ABI in this case, I agree. This is not difficult though. And this question is relevant only if one allows taking the address of a nested function, because as long as it is called only locally, the compiler can use whatever ABI it wants.
- jcranmer 7d agoOkay, so you accept that nested functions have a different ABI than C++ lambdas. And it looks like you accept that neither ABI is going to change. So long as the two features have different ABIs, they cannot be compatible with one another. > And this question is relevant only if one allows taking the address of a nested function And that question is very relevant since taking the address of such functions (to pass to other functions, e.g., qsort) is one of the main use cases for their existence.
- uecker 7d agoThe ABI does not need to be compatible, because there is no way to call a C++ lambda directly from C. If you take the address of a lambda function you get a pointer to an object of anonymous type, so you can not pass it to qsort, and qsort would also not know how to call this. But if we added a feature similar to std::function_ref to C (i.e. a wide function pointer type), then such a type could be used to call both, nested functions and C++'s lambdas, and - in fact - many callable entities from other languages too. But for C++'s lambdas this would always involve a compiler generated thunk that adapts the ABI. This is also exactly what happens in C++ if you use std::function, because even in C++ you can not pass the address of lambda to a function without first erasing the type and creating the thunk. So there is no compatibility problem.
- uecker 7d agoAn example showing this equivalency is this: https://godbolt.org/z/vEP5G9Pfr https://godbolt.org/z/vEP5G9Pfr Similar to how std::function_ref creates a thunk in C++ that calls the lambda so that it has a generic type-erased API that can be passed to non-templates, a conversion to a wide pointer would create a thunk that adapts the call from the nested function pointer ABI (that already exits for other languages also in LLVM whether we standardize the C feature or not) to whatever the lambda needs. Edit: slightly updated example.
- jcranmer 7d agoAn example showing nonequivalency is this: https://godbolt.org/z/PxP4vrfM1 https://godbolt.org/z/PxP4vrfM1 Showing things that are optimized by fully inlining into one function don't actually demonstrate equivalency, because you end up omitting anything that might actually evidence a difference in the semantics. And I get that, for your use cases, those differences might not matter. But as a compiler engineer, I can't say that only those use cases matter and therefore they're equivalent for all practical purposes. It is also very unhelpful when you insist that this is "compatible" with other languages, where "compatible" actually means "compatible, if you put in a bunch of work in both languages to make something that makes them compatible, none of which I'm actually describing." Especially when there are competing proposals that do have compatibility in the sense of "I don't have to modify the C++ compiler to let it use this thing."