Whole program optimization is an umbrella term for various types and techniques of optimizations that may generate effects beyond "local" optimizations -- that is, optimizations that the compiler applies to delimited contexts, such as file level, translation level or module/namespace level.
Below I will try to show some of these types of design choices that the compiler (and not the language) may exhibit in implementing whole-program or link time optimizations, and how these design choices may or may not impact the language specification or expectations. The examples below are wonderfully detailed in the already mentioned LLVM Link Time Optimization: Design and Implementation, and I also suggest reading about incremental compilation and the associated metadata in this other context.
Metadata for dropping
Say, the standard library defines a lot of types and functions, but one particular program may only need a fraction, less than 1% of all this code base.1
To be able to optimize the whole program by dropping all unnecessary code, the distributed compiled library needs to be annotated with enough metadata to make this possible.
For example, the library defines the functions a()
, b()
, and c()
, and the program code only explicitly calls a()
. But if a()
internally calls b()
, there is enough metadata to the compiler to drop only c()
.
Metadata or source code for inline
You note that the compiler and code base uses a lot of leaf, simple and small functions, a sweet spot for inlining. So a module or library may have these public functions compiled and published, but there is the possibly of inlined functions to be duplicated all over the place, as copied and optimized away in various vestigial forms.
Here, it's possible to see some impacts on language specifications or expectations. For example, placing a breakpoint on one leaf function will not pause at all invocations of this function, as some (most? all?) call sites may be replaced by the optimizing compiler.
That said, in whole program optimization you may want this duplication and mutation to occur outside the defining locus of the function, after the library is compiled and deployed.
For this to occur, the compiled library also needs to exhibit some duplication itself, so that the compiler then can further replace outside calling sites with more duplicated and mutated copies of original function.
This "duplicated for optimization" form needs to exist in some pre-compiled or even in embed source form, in a sufficiently high level to permit the compiler to understand and further optimize it away.
Source code only, all way down
This may sound extreme, but there is a sense that de C++ Standard Template Library does: There are no binaries, only source code. I think this also applies to the MLton of the question, because afaik ML language libraries are defined entirely in terms of pure source code, sprinkled with few compiler intrinsics as necessary.
In a language and standard library designed this way, having any binary library always embed with their source counterparts, would facilitate whole program optimizations as the compiler may then choose to try recompile the already compiled sources, without needing to decompile some more complicated metadata or binary format.
In this last example, the difference between modular compilation and whole-program optimization may end blurred, but in a sense, whole-program optimization is a form of whole-program recompilation, in a latter time.
TL:DR; Metadata and source code in binaries, or no binaries at all
1 I tried to find, with no luck, one page describing incremental compilation, possibly about Rust, Swift, Zig or Go, that showed numbers about how many little symbols are necessary for a "Hello world". I vaguely remembered this page because the intervelaing nature of incremental compilation and inter modular optimizations.