Inference
You can start a Julia session, edit (for example to insert print
statements), and then replace Core.Compiler
in your running session by navigating to base/compiler
and executing include("compiler.jl")
. This trick typically leads to much faster development than if you rebuild Julia for each change.
A convenient entry point into inference is typeinf_code
. Here’s a demo running inference on convert(Int, UInt(1))
:
Much of the hardest work for inlining runs in . However, if your question is “why didn’t my function inline?” then you will most likely be interested in isinlineable
and its primary callee, inline_worthy
. isinlineable
handles a number of special cases (e.g., critical functions like next
and done
, incorporating a bonus for functions that return tuples, etc.). The main decision-making happens in inline_worthy
, which returns true
if the function should be inlined.
inline_worthy
implements a cost-model, where “cheap” functions get inlined; more specifically, we inline functions if their anticipated run-time is not large compared to the time it would take to issue a call to them if they were not inlined. The cost-model is extremely simple and ignores many important details: for example, all for
loops are analyzed as if they will be executed once, and the cost of an includes the summed cost of all branches. It’s also worth acknowledging that we currently lack a suite of functions suitable for testing how well the cost model predicts the actual run-time cost, although provides a great deal of indirect information about the successes and failures of any modification to the inlining algorithm.
We supplement this low-level lookup table with a number of special cases. For example, an :invoke
expression (a call for which all input and output types were inferred in advance) is assigned a fixed cost (currently 20 cycles). In contrast, a :call
expression, for functions other than intrinsics/builtins, indicates that the call will require dynamic dispatch, in which case we assign a cost set by Params.inline_nonleaf_penalty
(currently set at 1000). Note that this is not a “first-principles” estimate of the raw cost of dynamic dispatch, but a mere heuristic indicating that dynamic dispatch is extremely expensive.
Each statement gets analyzed for its total cost in a function called statement_cost
. You can run this yourself by following the sketch below, where f
is your function and tt
is the Tuple-type of the arguments: