| # 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. |