The callback encapsulation utility will wrap a call, either direct or
transitive, such that information can be propagated through it either
without modifying the actual call (or the function interfaces).
Callback encapsulation is a semantic no-op. Thus, the code is always
semantically equivalent to the original input and there is no need to
interpret any of the metadata used by the encoding. Unaware passes will
behave correctly at all times.
This allows us to perform argument promotion and other IPOs that modify
the function signature even if (1) the signatures are fixed (externally
visible declarations) and even if (2) the new function signature is not
known to be better per se which means we might need to revert back to
the original (or some derived form).
The first use case appears when we look at transitive calls, e.g.
through pthread_create. We want the payload pointer (often a struct)
to be promoted (=unpacked) at the call site in order to enable other
IPOs, e.g. constant propagation. However, this is so far impossible
because the pthread_create interface is fixed. With this functionality
we can unpack the payload, propagate information from call site to the
callee and vice versa, before we at the end fold everything to the
original version again. See the example in CallbackEncapsulate.h.
The second use case appears for example when large structs are passed to
a function and we know we can promote (=unpack) them if we want. While
doing so will enable other IPOs, as described above, it might also make
it (basically) impossible to pack them (properly) again. Thus, we might
be stuck with a lot of arguments passed explicitly which can be
significantly more expensive than the original code was. To allow
promotion of large structs without the risk of paying this price we can
encapsulate the call site in a callback. The original way of passing
arguments is preserved but we still expose (almost) the same
Callback encapsulation uses !callback metadata already available in
the IR as well as !rpl_cs and !rpl_acs metadata which is new. Since
the !rpl_cs and !rpl_acs can be safely ignored, we will delay the
description in the language reference until we gained more experience
with it (and performed potentially necessary modifications).