Exceptions are normal Prolog terms that are handled specially by the 
PREDICATE macro when they are used by a C++ throw, and 
converted into Prolog exceptions. The exception term may not be unbound; 
that is, throw(_) must raise an error. The C++ code and underlying C 
code do not explicitly check for the term being a variable, and 
behaviour of raising an exception that is an unbound term is undefined, 
including the possibility of causing a crash or corrupting data.
The Prolog exception term error(Formal, _) is special. If the 2nd 
argument of error/2 
is undefined, and the term is thrown, the system finds the catcher (if 
any), and calls the hooks in library(prolog_stack) to add the context 
and stack trace information when appropriate. That is, throw PlDomainError(Domain,Culprit) 
ends up doing the same thing as calling
PL_domain_error(Domain,Culprit) which internally 
calls
PL_raise_exception() and returns control back to Prolog.
The VM handling of calling to C finds the FALSE return 
code, checks for the pending exception and propagates the exception into 
the Prolog environment. As the term references (term_t) 
used to create the exception are lost while returning from the foreign 
function we need some way to protect them. That is done using a global term_t 
handle that is allocated at the epoch of Prolog.
PL_raise_exception() sets this to the term using PL_put_term().
PL_exception(0) returns the global exception term_t 
if it is bound and 0 otherwise.
Special care needs to be taken with data backtracking using
PL_discard_foreign_frame() or PL_close_query() because 
that will invalidate the exception term. So, between raising the 
exception and returning control back to Prolog we must make sure not to 
do anything that invalidates the exception term. If you suspect 
something like that to happen, use the debugger with a breakpoint on
__do_undo__LD() defined in pl-wam.c.
In order to always preserve Prolog exceptions and return as quickly as possible to Prolog on an exception, some of the C++ classes can throw an exception in their destructor. This is theoretically a dangerous thing to do, and can lead to a crash or program termination if the destructor is invoked as part of handling another exception.