I think I understood why reabstraction is needed here, please correct me if I'm wrong.
Code that puts function into array does not know how that array will be used after that. It may end up being used from the generic code, which would need to reabstract function signature into something like (T) -> U. But to perform reabstraction from original representation compiler needs to know specific types of the arguments and return values. At the site there function gets reabstracted from Array.Element into (T) -> U, this information is not available. So, instead compiler places into array the representation which is generic enough to be able to create any representation out of it. Which is - pass every argument and return value by reference.
But it is still not clear to me why multiple thunk helpers are needed.
You are correct as to why the thunks are needed. It doesn't look like the compiler is directly generating multiple thunks, but it is optimizing together the reabstraction thunks and partial application forwarders (which takes the capture context for a closure and expands it out into multiple arguments to be used by the closure implementation function) into single functions, causing multiple functions to be formed.
Thanks for explanation. For the case of inlining partial apply forwarders this makes sense. Do you know by any chance what happens with duplicated reabstraction thunk helpers if partial application forwarders cannot be inlined?
Are you talking about the number of functions emitted, or the number of closures allocated? It will have to allocate a separate closure for each reabstracted closure. They should all share one thunk function implementation.
Number of functions (reabstraction thunk helpers). Running with -O -whole-module-optimization, I do see multiple functions being produced - both in the output of nm and in LLVM IR. In LLVM IR I can also see that multiple metadata's and capture descriptors are being created.