The Compiler in OpenJ9 uses C++ Exceptions to signal an error during the compilation of a method. However, because errors can occur even before a compilation can occur, there are multiple nested try/catch blocks in the compiler runtime.
The nested blocks can be visualized as follows:
try
{
// Entry Processing
try
{
// Compiler Infrastructure Initialization
try
{
// Compilation Initialization
}
try
{
// Compilation
}
}
}
As described in the Memory Manager documentation, the Compiler uses the
J9::SegmentCache
to preallocate and cache memory to speed up memory
allocation across multiple compilations. This first try/catch block is used
to guard this initialization; it can be found in
TR::CompilationInfoPerThread::processEntries
. This outer most try/catch
block only exists when doing compilations on Compilation Threads; if the
compilation occurs on an Application Thread, then the J9::SegmentCache
is
not created.
The catch block here only expects to catch std::bad_alloc
.
When the compiler decides to compile a method, the
TR::CompilationInfoPerThreadBase::compile
(with three arguments) is invoked.
This method is used to initialize the scratch memory infrastructure (which
provides Compilation Threads with memory needed during the compilation) as
well as other compilation infrastructure related initialization. Since an
exception can be thrown during this initialization, and because between the
beginning of Entry Processing and Compilation Infrastructure Initialization
there are actions performed (such as acquiring VM Access) that need to be
undone on an error condition, a (nested) try/catch block is added.
The catch block exists primarily to catch std::bad_alloc
. However, it also
catches J9::JITShutdown
and std::exception
in case in the future there is
an unguarded call to acquireVMAccessIfNeeded
which can throw.
After the Compilation Infrastructure is initialized, wrappedCompile
is
invoked; this method is a static method that is protected by j9sig_protect
and is used to wrap the compilation with a JVM defined signal handler.
wrappedCompile
first initializes the compilation before actually starting
the compilation. As there can be exceptions thrown during initialization,
the code is guarded with a try/catch.
The catch block expects to catch J9::JITShutdown
, std::bad_alloc
, or
std::exception
.
If everything is successful until this point, wrappedCompile
then invokes
TR::CompilationInfoPerThreadBase::compile
(with six arguments). The
try/catch found in here is the one that guards the actual compilation, and
is used to catch errors found during the compilation of a method.
The catch block excepts to catch everything. It then re-throws and re-catches
the exception in TR::CompilationInfoPerThreadBase::processException
to
determine the type of exception throw, and what action needs to be taken
accordingly.