itanium-cxx-abi / cxx-abi

C++ ABI Summary
504 stars 94 forks source link

Lambda ABI mismatch #141

Open nandor opened 2 years ago

nandor commented 2 years ago

Compiler inlining of methods which define lambdas leads to issues. If the creation site is inlined and compiled with a separate compiler, the linker can still decide to replace the function with an implementation from another compiler capturing items in a different order. Since the creation site and the lambda implementation do not match, incorrect parameters are passed to the function.

Is this undefined behaviour or an issue with the ABI?

A minimal example to reproduce the error can be found here: https://github.com/nandor/lambda-abi-error

RokerHRO commented 2 years ago

I wonder why you can give a lambda with non-empty catch clause to a std::function<void(void)>.

https://en.cppreference.com/w/cpp/language/lambda#ClosureType::operator.28.29.28params.29

This user-defined conversion function is only defined if the capture list of the lambda-expression is empty.

aaronpuchert commented 2 years ago

I wonder why you can give a lambda with non-empty catch clause to a std::function<void(void)>.

https://en.cppreference.com/w/cpp/language/lambda#ClosureType::operator.28.29.28params.29

This user-defined conversion function is only defined if the capture list of the lambda-expression is empty.

The user-defined conversion function to function pointer type shouldn't be used here, because std::function can directly be constructed from a Callable object, that's overload (5) here. The usual implementation is that it copies over the Callable object into dynamically allocated storage. See the note below:

When the target is a function pointer or a std::reference_wrapper, small object optimization is guaranteed, that is, these targets are always directly stored inside the std::function object, no dynamic allocation takes place. Other large objects may be constructed in dynamic allocated storage and accessed by the std::function object through a pointer.

rjmccall commented 2 years ago

We've previously discussed this here. Our conclusion then appears to have been that we should force lambdas to have internal linkage. Clang seems to have never implemented that; in r151029, Doug Gregor gave lambdas external linkage in the situations where they have a stable mangling. It looks like GCC and ICC are doing the same. So effectively compilers are implementing option 1 from that mailing list post.

If compilers aren't going to give lambdas internal linkage in these situations, the ABI needs to define a layout for them. As far as I can tell, Clang, GCC, and ICC are all trying to use the same layout rule: explicit captures are added in declaration order, then implicit captures are added in the source order of their ODR use in the lambda body. The exception which causes this incompatibility is that GCC seems to order implicit this arguments (and only implicit ones) after the formal call arguments. I would argue that that's just a GCC bug, and the this argument should be ordered as if it were written in source. @jicama, do you agree?

joker-eph commented 1 year ago

Filed: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109963