> The issue with this approach is that the more you can control, the less the compiler can assume.
That's absolutely true, but there's a workaround, albeit a complicated one. The compiler internals need the same kind of constraints or traits that abstract code such as language interfaces or template parameters can have. These can then be used to constrain the internals in a way that then would allow assumptions to be safely "plumbed through" the various layers. The (big!) challenge here is that these abstractions haven't been well-developed in the industry. Certainly nowhere near as well as the typical runtime "type theory" as seen in modern languages.
> some very basic ones like allocating heap memory.
Well... this is sort-of my point! For example, what's the fundamental difference between allocating memory in some heap[1] structure at runtime and a compiler allocating members in a struct/record/class for optimal packing?
IMHO, not much.
E.g.: Watch this talk by Andrei Alexandrescu titled "std::allocator Is to Allocation what std::vector Is to Vexation": https://www.youtube.com/watch?v=LIb3L4vKZ7U
It really opened my eyes to how one could very elegantly make a very complex and high-performance heap allocator from trivial parts composed at compile-time.
There's no reason that a nearly identical abstraction couldn't also be used to efficiently "bin pack" variables into a struct. E.g.: accounting for alignment, collecting like-sized items into contiguous sections, extra padding for "lock" objects to prevent cache issues, etc...
This is what my dream is: that a struct might just be treated as a sort-of comptime heap without deletions. Or even with deletions, allowing fun stuff like type algebra that supports division. I.e.: The SELECT or PROJECT-AWAY operators!
There was some experimental work done in the Jai language to support this kind of thing, allowing layouts such as structure-of-arrays or arrays-of-structures to be defined in code but natively implemented by the compiler as-if it was a built-in capability.
[1] Not really a traditional heap in most language runtimes these days. Typically a combination of various different allocators specialised for small, medium, and large allocations.
PS: The biggest issue I'm aware of with ideas like mine is that tab-complete and IDE assistance becomes very difficult to implement. On the other hand, keeping the full compiler running and directly implementing the LSP can help mitigate this... to a degree. Unsolved problems definitely remain!