Exception-Directed Optimization (EDO) enables more aggressive inlining of methods that throw and catch exceptions to facilitate the transformation of a throw operation into a goto by Value Propagation (VP). Typically, profiling is used to determine which throw-catch pairs happen often enough to warrant extra inlining.
In OpenJ9, the EDO mechanism starts by adding a snippet that decrements the
TR_PersistentJittedBodyInfo._counter
every time a catch block is executed. This is done in
VMgenerateCatchBlockBBStartPrologue()
and is gated by a test to fej9->shouldPerformEDO(block, comp)
. The test makes
sure that we don't instrument big methods (that have more than TR::Options::_catchSamplingSizeThreshold
nodes) or if EDO is disabled with -Xjit:disableEDO
.
When TR_PersistentJittedBodyInfo._counter
reaches zero, the snippet will execute a jump to a recompilation
helper and the method will be recompiled at the next optimization level (typically hot).
Since higher optimization levels are more aggressive with inlining, sometimes this is enough
to bring the throw and the catch in the method being compiled. If that happens, VP
may change the throw into a goto (this transformation can be disabled with -Xjit:disableThrowToGoto
),
and the process stops there.
The second step is to profile the frequency of the catch block in the newly compiled body with
TR_CatchBlockProfiler
which is created in J9::Recompilation::beforeOptimization().
This is a very simple profiler that only includes two counters: one for throws and one
for catch operations (the counter for throws appears to be unused by the optimizer).
These counters are applied globally for the entire method and not per catch block/throw call-site.
The instrumentation is done by the recompilationModifier
optimization which goes over the list of registered profilers and calls
modifyTrees() on them.
In particular, TR_CatchBlockProfiler::modifyTrees() allocates a TR_CatchBlockProfileInfo
object with persistent memory (if it doesn't already exist) and, for each catch block it inserts
an increment instruction for the TR_CatchBlockProfileInfo._catchCounter
field.
It's important to note that recompilationModifier
is an optimization pass enabled on-demand
in Optimization::switchToProfiling()
if a few conditions are true:
- the optimizer decides to switch to profiling for some reason
- the compilation does not use "
optServer
" mode (unlessAggressiveSwitchingToProfiling
is on)
The third step is to recompile again the method and to use the collected profiling data to guide the inliner to inline more on the throw-catch call-graph. The TR_EstimateCodeSize estimator has a boolean field called _aggressivelyInlineThrows which is set to true when the method has catch-block profile information and the catch counter is greater than TR_CatchBlockProfileInfo::EDOThreshold. Under those conditions the estimator under-estimates the size of the callees with throws so that they are more likely to be inlined. If inlining succeeds and brings the throw into the code for the method being compiled, the VP may then transform the throw into a goto operation.