The current inlining is sort of an ad hoc implementation of a more general āmost common recipientā kind of optimization. The āpureā implementation of if
is just a message send, and the method dispatch logic takes care of everything (e.g. routes to the True or False class, or something else).
0 12 PushInstVar 'maybeABooleanOrNot'
1 A1 00 SendMsg 'if'
The āgeneralā optimization would be to check for the most common message recipients, and then inline those methods. The optimization cases we care about are: the first argument to if
is True
or False
and the second and third arguments are Function
s. For the classic āif() {} {}ā syntax, we know at compile time that the second and third args are functions. And, we know at compile time that True:if and False:if just call these functions. So, the optimized logic might look like this, in pseudocode:
if (maybeABoolean.isKindOf(True)) {
// .. inlined True:if -> inlined trueFunction
} else if (maybeABoolean.isKindOf(False)) {
// .. inlined False:if -> inlined falseFunction
} else {
// .. normal dispatch, if(maybeABoolean, trueFunction, falseFunction)
}
We can imagine this sort of optimization for any number of common / internal methods: check for one or two common cases, else fall back to the āslowā path. This is exactly what sclang if
does now, EXCEPT that it assumes our slow path is always a āNon-Boolean in Testā error and sort of hardcodes that logic into the primitives.
So, making this behave in a consistent way (e.g. if
really IS just a normal method) only entails adding a fall-thru case to the inlined code, which is itself just a more general and correct way to optimize this kind of case - basically, do the dispatch instead of assuming an error.
Adding the extra two or three bytecode instructions could cause some very small performance degradations. But, thinking about it an alternate way: introducing a general optimization where we can inline method calls where there is a high likelihood of one particular recipient (where if
is just one case of thisā¦) would probably have a large, cross-cutting performance improvements, even if the individual if
case was incrementally slower.
If Iām thinking about it right, the performance slowdown would only really come in the form of more memory usage / cache misses: for the 99% case (e.g. we hit our optimization case), the interpreter would execute the same bytecode as it does now - for the 1% case where we donāt have a Boolean, we are currently throwing an error - this would not get importantly slower, and in any case incrementally slower performance in an error case is not really important.