It's ultimately a hardware-dependent answer. Self-modifying code fell out of fashion in the 1990's, once pipelined and cached code execution became the norm in desktop computing. From that point forward, correct answers to how to optimally use your hardware become complex to reason about, but generally follow the paradigms of "data-driven" coding: you're designing code that the CPU understands how to pipeline(and therefore is light on branching and indirection during inner loops), and data that the CPU can cache predictably(which leads to flat, array-like structures that a loop will traverse forward through).
Therefore what compilers will actually do is reorder program behavior towards optimal pipeline usage(where doing so doesn't break the spec). This has clear downsides for any programming style that relies on knowing instruction-level behavior. And it is so hard to keep up with the exactly optimal instructions across successive generations of CPU that in the majority of cases, humans just never get around to attempting hand optimization.
The benefit of defining a VM is that you can define whatever is optimal in terms of pragmatic convenience - if you want to write programs that have a certain approach to optimization, you can make some instructions, uses of memory or styles of coding relatively faster or slower, and this leads to an interesting play space for programmers who wish to puzzle through optimization problems. But unless it also happens to represent the actual hardware, it's not going to achieve any particular goal for real performance or energy usage - at least, not immediately. Widespread adoption motivates successively more optimal implementations. But that logic makes it hard to justify any "starting over" kind of effort, because then you end up at the conclusion that the market is actually succeeding at gaining efficiency through its fast-moving generational improvements, even if it does simultaneously result in an environment of obsolescence as the ecosystem-as-a-whole moves forward and leaves some parts behind.
An alternate path forward, one which can integrate with the market, is to define a much more narrow language for each application you have in mind, and be non-particular about the implementation, thus allowing the implementation to become as optimal as possible given a general specification. This task leads, in the large scale, towards something like the VPRI STEPS project, where layers of small languages build off of each other into a graphical desktop environment.