Thanks, I missed the region signature converter part in the manual. It was a bit challenging to figure out the interaction between the signature converter and the argument materialisation logic though.
I ended up with a signature converter in a FuncOp rewriter pattern that splits the structs into separate arguments per fields, and an argument materialiser that reassembles the struct from the split arguments. This combined with a type rewriter for function pointers, and a CallOp-like rewriter pattern for unpacking the structs before actually calling the functions.
This was all hacked into our LLVM lowering pass, I am sure I could make it better, especially as the pass is now tightly tied to the target architecture. I suppose the long term approach in our case would be to lower to CIL instead.