Hi Seth,
I like the clever budgeting approach, and we've indeed seen similar issues as you describe with approaches we've tried: The megamorphic stub cache is pretty damn fast and it's hard to beat it with dynamic structure walks. Performance with stub cache is hard to predict though, since it's non-locally polluted. If we can get close with e.g., an approach that looks for single outgoing transitions (we've thought about the same fwiw), that might be globally beneficial.
The approach of reusing the parent map is definitely clever, but seems pretty hard to guarantee that the object doesn't flow anywhere in unoptimized code. I think we'll pretty quickly end up with performance footguns (or correctness/security issues).
I agree with Leszek though that we should look at more fundamental solutions to especially the subclassing problem. Subclasses will typically have exactly the same shape for the shared superclass part, but still those accesses (also non-transitioning and non-stores) are polymorphic. We should be able to come up with a fundamental change to the object model that solves this.
As Leszek mentioned, we've lately been thinking along the lines of interfaces/vtables to solve this. For each base class we could create an interface that describes all megamorphic properties on the class hierarchy, even properties not supported by the base class itself. Subclasses "implement the interface" with a vtable that describes for each property supported by the interface how to access the property. The interface can also point back to all classes that participate in the interface. That way we have steady (but probably not as fast as stubcache) baseline performance, and have detailed information we can feed into the optimizing compiler. It's still a very early idea though.
Other ideas involve separating receiver shape from prototype (and even native context). We're also thinking of ways to get more static guarantees wrt the shape of class hierarchies.
Do keep the ideas coming though!
cheers,
Toon