"Few people care about all things simultaneously, so linking seems overcomplicated to everyone. But all these concerns add to complexity individually, and the tension between them also increases complexity."
I think that most compilers should not compile to machine code. They should compile to an intermediate bytecode (only doing relatively generic optimizations), something like LLVM but with precondition / postcondition annotations. Call this phase 1. The operating system then takes that bytecode and on first-run (or at any other time if a program explicitly asks for it) compiles it to machine code, doing whole-program optimization, for the specific machine. Call this phase 2.
The phase 1 compiler may split functions into a version with the original semantics, and maybe versions that require preconditions / post-conditions. Say, a hashmap constructor that requires the size to be a prime (that throws an error otherwise) may get split into a version that checks if the size is a prime that falls through into a function that just assumes the size is a prime, with a precondition that the size is a prime. This can then be used by the phase 2 compiler when appropriate.
For public functions, the phase 2 compiler may decide to copy (a) functions from the shared library to the program itself if there is enough incentive to do so. Say, a function that does an expensive check that the compiler knows isn't necessary, but the pass 1 compiler didn't split it out. The phase 2 compiler saves information regarding what optimizations were done / useful, which can be passed back to the phase 1 compiler when appropriate.
(For cases where a compiler on the target device isn't really appropriate, and/or the hardware of the target is fixed, you can cross-compile. Again: "most".)
This is true of all software, actually.