WebAssembly sits at the crux of directly opposing forces:

If it goes higher-level:

* Compiled applications get smaller because each instruction corresponds to a higher-level semantic behavior and contains more information. Think how a virtual method call is a single instruction in the JVM but several instructions in x64.

* Compiled applications get smaller because runtime facilities can be baked into the browser and shared across all apps. GC is the big one but things like object representation, strings, "standard library" functions, etc.

* More host-level optimization opportunities open up. The JVM JIT can do lots of optimizations because it understands directly what things like interface and virtual calls are and doesn't have to try to reconstitute that information by pattern matching on lower-level instruction sequences.

If it goes lower-level:

* It's a viable target for more diverse languages and paradigms. You can compile other languages to JVM bytecode, but the less like Java your language is, the harder that becomes. The VM has a grain to it. WebAssembly, by virtue of having fewer things baked in, is more open to a varied languages. C/C++ are the big ones because any memory-safe instruction set makes it really hard to support those.

* More application-level optimization opportunities open up. Optimizing compilers can output code that's closer to the metal. They can take advantages of constraints in the source language that the VM may not know about and generate code specific to that.

* Peak execution speed is higher. If your instruction set lets you directly map to something close to what the CPU executes, you can take maximum advantage of it. Despite decades of JIT engineering, C/C++ are still the fastest for that reason.

* The instruction set is simpler. That makes it easier to implement, target, optimize, security audit, and build tooling for.

There is no Goldilocks instruction set that gives you all of these. Adding instructions for GC is going to make it much more complex and is deadweight for languages like Rust and C that won't use it. Defining how the GC works is going to be really difficult given languages like Python (ref-counting), C# (finalizers), etc. where specific GC policy decisions are user-visible.

Making the instruction set statically typed is what you want for low level typed languages. But it makes it harder for dynamically-typed languages to be implemented efficiently. Every dynamically-typed language that compiles to JS gets to reuse the incredible JITs inside JS implementations because JS is itself dynamically-typed. But if you compile, say, Python, to WebAssembly, do you ship a Python bytecode -> WebAssembly JIT inside the application? Does every language have to do that?

Having strings means you need to pick a string format, which is basically impossible since every language out there makes different, incompatible choices for string representation (null-terminated? length-prefixed? null-clean byte-arrays? UTF-16? UTF-8? multi-encoding?). But not having strings means every app has to include a string library in its runtime and means you can't reliably pass strings around in interop.

No amount of hard work is going to magically fix these because they are directly opposed. I think WebAssembly is really interesting, but one of the things that has always turned me off about it is that its proponents don't often acknowledge the hard trade-offs that have to be made.