I think there's a conflict between FFIs in practice and theoretical FFIs. The few papers I've read for sound FFIs between languages A and B rely on the fact that the common interop target is a high-level language whose features subsumes the features of A and B. ABI, calling convention, etc. are merely implementation details.
So from this perspective, the interop target should actually not be the lowest common denominator, but the opposite: a target that can describe all possible user languages in a common format.
In fact, I would actually argue that the implementation details are NOT that interesting and only a distraction: that we need an abstract way of declaring compatibility. For instance, one can imagine a high-level FFI target where function types are parameterized by their calling convention i.e.:
`foo: (VOID<C>->VOID<C>)<C>`
`bar: (UNIT<Rust> -> UNIT<RUST>)<RUST>`
and attempting to call `bar` from `foo` involves a compiler intrinsic like `RUST_CALL(bar)()` to convert the type (and the calling convention) appropriately.
So from this perspective, the interop target should actually not be the lowest common denominator, but the opposite: a target that can describe all possible user languages in a common format.
In fact, I would actually argue that the implementation details are NOT that interesting and only a distraction: that we need an abstract way of declaring compatibility. For instance, one can imagine a high-level FFI target where function types are parameterized by their calling convention i.e.:
`foo: (VOID<C>->VOID<C>)<C>`
`bar: (UNIT<Rust> -> UNIT<RUST>)<RUST>`
and attempting to call `bar` from `foo` involves a compiler intrinsic like `RUST_CALL(bar)()` to convert the type (and the calling convention) appropriately.