Is that lookup done all the time (for each invocation of the same method):? Or is it done at a single point in time, like a JIT, but after that the method is “fixed”?
With the possibility to intercept/replace code at runtime (at any time, not just at a resolve/jit stage) it must be very hard for the runtime to optimize calls (i.e make direct calls instead of indirect via method pointer lookup)?
I’m not familiar with how this works in any runtime (V8, Hotspot, ...) so I’m curious which runtimes actually pay one extra method pointer lookup forever and which don’t. I’m guessing the answer for nearly all of them is “it depends”.
Many languages use "inline method caching" - that is, a "call_dynamic x_named_method" bytecode is replaced with a "guard_and_call_static 0xfffffff" once the callsite is evaluated. In some languages invalidating this cache by calling a method with polymorphic arguments or altering the receiving class can be extremely expensive, so there are usually second-order optimizations applied.
Objective-C: Takes one method pointer indirection / jmp trampoline forever, but caches "selectors" on the class so that they become a simple lookup rather than a full evaluation. See sibling comments for better write-ups than I could find.
Ruby: Also uses inline method caching - when a method is resolved that callsite is replaced with a jump straight to the resolved method in the bytecode. Invalidation used to occur any time a class was modified in any way but has been made more specific over time: https://github.com/charliesome/charlie.bz/blob/master/posts/...
”To speed the messaging process, the runtime system caches the selectors and addresses of methods as they are used. There’s a separate cache for each class, and it can contain selectors for inherited methods as well as for methods defined in the class. Before searching the dispatch tables, the messaging routine first checks the cache of the receiving object’s class (on the theory that a method that was used once may likely be used again). If the method selector is in the cache, messaging is only slightly slower than a function call. Once a program has been running long enough to “warm up” its caches, almost all the messages it sends find a cached method. Caches grow dynamically to accommodate new messages as the program runs.”
The lookup happens on each invocation. It’s alway indirect, and the implementation of a method can be changed at runtime. Method calls aren’t optimized to be direct invocations.
There's also the "Illustrated history of objc_msgSend", although it appears a bit dated, it has some nice commentary on the evolution of this very performance-critical part of the objective-c runtime:
http://sealiesoftware.com/msg/index.html
With the possibility to intercept/replace code at runtime (at any time, not just at a resolve/jit stage) it must be very hard for the runtime to optimize calls (i.e make direct calls instead of indirect via method pointer lookup)?
I’m not familiar with how this works in any runtime (V8, Hotspot, ...) so I’m curious which runtimes actually pay one extra method pointer lookup forever and which don’t. I’m guessing the answer for nearly all of them is “it depends”.