Hacker News new | ask | show | jobs
by mojuba 2437 days ago
Always wondered about this too, but there doesn't seem to be a way of knowing which implementation your code will end up calling in Objective-C. "Final" would be helpful but there's no "final" in the language.
1 comments

Speculative inlining driven by heuristics or PGO should always be possible. Same thing is done for virtual functions in C++ (or any indirect call really).
Objective-C's OO model is far more dynamic than you may realize. There're no guarantees even on the object reference type at run time, i.e. that the method you are calling will be applied to an object of a specific type or compatible. ObjC is very permissive in this regard, though it can give warnings in certain cases but they are never 100% precise (from my experience anyway). You could run analysis on the entire end product and still be unsure of what's going to happen at run time.
This is also true for languages like Self as well which pioneered inline (and polymorphic) caches in the first place. Self did it using JITs, but you "only" need a sufficient hit rate to make up for the check+branch to justify inlining the most likely option(s) at compile time.

The interesting thing is that by inlining, for the inline case you will often gain additional type information. E.g. to take an example from Ruby, since I don't know Objective C very well. In isolation you have no way of telling what type "foo + 1 + 2 + 3" will return, as it depends entirely on "foo". But lets say most call-sites calling the method where this expression is found passes an integer.

If I can guarantee that "foo + 1" is an Integer addition, then I know it will return an Integer, and so I know the same addition method will be used for the next addition (and by extension the next as well), so I can turn the above into the following Ruby-ish pseudo-code instead:

    if foo.is_a?(Integer)
        # By recursively inlining, I know not just the type of foo, but the type of the full expression.
        inlined foo + 1 + 2 + 3
    else
        foo.send(:+, 1).send(:+, 2).send(:+, 3)
    end
Even when you can't safely inline the actual calls, you can often elide checks or resort to more specialized method caching.
Yes, of course with a JIT you can do much better than an AOT compiler, and for dynamic languages is pretty much required to get reasonable performance.
Well, compared to profile guided optimization as mentioned by the other commenter earlier, that's really only the case if the profile of called methods vary greatly between runs.

The polymorphic inline caching from Self for example is guided by collecting simple stats. Tracing does the same. A JIT ensures those stats are always completely up to date, but nothing stops you from saving it and using it for an AOT compiler as well.

But often even that is overkill, as you can often statically deduce a lot about the types a method is likely to get called with by simply looking at the call sites, and most programs have very static call profiles.

Doesn't matter, at some point there's a indirect function call and the compiler can try to guess the target, inline it and add an address check that, on failure falls back to the slow path.
This actually seems like it’d be quite beneficial, as I’d assume 90+% of method call targets can be statically guessed just by looking at the code (to increase this ratio even more, I’m sure Apple could even ignore Cocoa methods that use the forwarding machinery).