Along with the location of an exception handler, each entry of the
exception table also contains the stack depth of the `try` instruction
and a boolean `lasti` value, which indicates whether the instruction
offset of the raising instruction should be pushed to the stack.
Handling an exception, once an exception table entry is found, consists
of the following steps:
1. pop values from the stack until it matches the stack depth for the handler.
2. if `lasti` is true, then push the offset that the exception was raised at.
3. push the exception to the stack.
4. jump to the target offset and resume execution.
Reraising Exceptions and `lasti`
--------------------------------
The purpose of pushing `lasti` to the stack is for cases where an exception
needs to be re-raised, and be associated with the original instruction that
raised it. This happens, for example, at the end of a `finally` block, when
any in-flight exception needs to be propagated on. As the frame's instruction
pointer now points into the finally block, a `RERAISE` instruction
(with `oparg > 0`) sets it to the `lasti` value from the stack.
Format of the exception table
-----------------------------
Conceptually, the exception table consists of a sequence of 5-tuples:
```
1.`start-offset` (inclusive)
2.`end-offset` (exclusive)
3.`target`
4.`stack-depth`
5.`push-lasti` (boolean)
```
All offsets and lengths are in code units, not bytes.
We want the format to be compact, but quickly searchable.
For it to be compact, it needs to have variable sized entries so that we can store common (small) offsets compactly, but handle large offsets if needed.
For it to be searchable quickly, we need to support binary search giving us log(n) performance in all cases.
Binary search typically assumes fixed size entries, but that is not necessary, as long as we can identify the start of an entry.
It is worth noting that the size (end-start) is always smaller than the end, so we encode the entries as:
`start, size, target, depth, push-lasti`.
Also, sizes are limited to 2**30 as the code length cannot exceed 2**31 and each code unit takes 2 bytes.
It also happens that depth is generally quite small.
So, we need to encode:
```
`start` (up to 30 bits)
`size` (up to 30 bits)
`target` (up to 30 bits)
`depth` (up to ~8 bits)
`lasti` (1 bit)
```
We need a marker for the start of the entry, so the first byte of entry will have the most significant bit set.
Since the most significant bit is reserved for marking the start of an entry, we have 7 bits per byte to encode offsets.
Encoding uses a standard varint encoding, but with only 7 bits instead of the usual 8.
The 8 bits of a byte are (msb left) SXdddddd where S is the start bit. X is the extend bit meaning that the next byte is required to extend the offset.
In addition, we combine `depth` and `lasti` into a single value, `((depth<<1)+lasti)`, before encoding.
For example, the exception entry:
```
`start`: 20
`end`: 28
`target`: 100
`depth`: 3
`lasti`: False
```
is encoded by first converting to the more compact four value form: