c885bdde1d
Change-Id: Ica33af158cca53c8e951e4b2582de83660e8a60d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/121851 Commit-Queue: Samir Jindel <sjindel@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
84 lines
4.1 KiB
Markdown
84 lines
4.1 KiB
Markdown
# Exceptions Implementation
|
|
|
|
This page describes how exceptions throwing and catching is implemented in the
|
|
VM.
|
|
|
|
## Intermediate Language
|
|
|
|
Dart VM's IL **does not** explicitly represent exceptional control flow in its
|
|
flow graph, there are **no** explicit exceptional edges connecting potentially
|
|
throwing instructions (e.g. calls) with corresponding catch blocks. Instead this
|
|
connection is defined at the block level: all exceptions that occur in any block
|
|
with the given `try_index` will be caught by `CatchBlockEntry` with the equal
|
|
`catch_try_index`.
|
|
|
|

|
|
|
|
For optimized code this means that data flow associated with exceptional control
|
|
flow is also represented implicitly: due to the absence of explicit exceptional
|
|
edges the data flow can't be represented using explicit phi-functions. Instead
|
|
in optimized code each `CatchBlockEntry` is treated almost as if it was an
|
|
independent entry into the function: for each variable `v` `CatchBlockEntry`
|
|
will contain a `Parameter(...)` instruction restoring variable state at catch
|
|
entry from a fixed location on the stack. When an exception is thrown runtime
|
|
system takes care of populating these stack slots with right values - current
|
|
state of corresponding local variables. It's easy to see a parallel between
|
|
these `Parameter(...)` instructions and `Phi(...)` instructions that would be
|
|
used if exception control flow would be explicit.
|
|
|
|

|
|
|
|
How does runtime system populate stack slots corresponding to these
|
|
`Parameter(...)` instructions? During compilation necessary information is
|
|
available in _deoptimization environment_ attached to the instruction. This
|
|
environment encodes the state of local variables in terms of SSA values i.e. if
|
|
we need to reconstruct unoptimized frame which SSA value should be stored into
|
|
the given local variable (see [Optimized
|
|
IL](compiler-pipeline-overview.md#optimized-il) for
|
|
an overview). However the way we use these information for exception handling is
|
|
slightly different in JIT and AOT modes.
|
|
|
|
### AOT mode
|
|
|
|
AOT mode does not support deoptimization and thus AOT compiler does not
|
|
associate any deoptimization metadata with generated code. Instead
|
|
deoptimization environments associated with instructions that can throw are
|
|
converted into `CatchEntryMoves` metadata during code generation and resulting
|
|
metadata is stored `RawCode::catch_entry_moves_maps_` in a compressed form.
|
|
|
|
`CatchEntryMoves` is essentially a sequence of moves which runtime needs to
|
|
perform to create the state that catch entry expects. There are three types of
|
|
moves:
|
|
|
|
* `*(FP + Dst) <- ObjectPool[PoolIndex]` - a move of a constant from an object
|
|
pool;
|
|
* `*(FP + Dst) <- *(FP + Src)` - a move of a tagged value;
|
|
* `*(FP + Dst) <- Box<Rep>(*(FP + Src))` - a boxing operation for an untagged
|
|
value;
|
|
|
|
When an exception is caught runtime decompresses the metadata associated with the
|
|
call site which has thrown an exception and uses it to prepare the state of the
|
|
stack for the catch block entry. See
|
|
`ExceptionHandlerFinder::{ReadCompressedCatchEntryMoves, ExecuteCatchEntryMoves}`.
|
|
|
|
NOTE: See [this
|
|
design/motivation](https://docs.google.com/a/google.com/document/d/1_vX8VkvHVA1Om7jjONiWLA325k_JmSZuvVClet-x-xM/edit?usp=sharing)
|
|
document for `CatchEntryMoves` metadata
|
|
|
|
### JIT mode
|
|
|
|
JIT mode heavily relies on deoptimization and all call instructions have (lazy)
|
|
deoptimization environments associated with them. These environments are
|
|
converted to [deoptimization
|
|
instructions](deoptimization.md#in-optimized-code)
|
|
during code generation and stored on the `Code` object.
|
|
|
|
When an exception is caught the runtime system converts deoptimization
|
|
environment associated with the call site that threw an exception into
|
|
`CatchEntryMoves` and then uses it to prepare the state of the stack for the
|
|
catch block entry. See `ExceptionHandlerFinder::{GetCatchEntryMovesFromDeopt, ExecuteCatchEntryMoves}`.
|
|
|
|
Constructing `CatchEntryMoves` dynamically from deoptimization instructions
|
|
allows to avoid unnecessary duplication of the metadata and save memory: as
|
|
deoptimization environments contain all information necessary for constructing
|
|
correct stack state. |