Skip to content

Commit

Permalink
gh-119786: remove part of devguide documentation which is duplicated …
Browse files Browse the repository at this point in the history
…in InternalDocs (#1334)
  • Loading branch information
iritkatriel authored Jul 31, 2024
1 parent c3950af commit d2a63a9
Showing 1 changed file with 4 additions and 27 deletions.
31 changes: 4 additions & 27 deletions internals/interpreter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -178,38 +178,15 @@ Then the interpreter function (``_PyEval_EvalFrameDefault()``) returns ``NULL``.

However, if an exception is raised in a ``try`` block, the interpreter must jump to the corresponding ``except`` or ``finally`` block.
In 3.10 and before, there was a separate "block stack" which was used to keep track of nesting ``try`` blocks.
In 3.11, this mechanism has been replaced by a statically generated table, ``code->co_exceptiontable``.
The advantage of this approach is that entering and leaving a ``try`` block normally does not execute any code, making execution faster.
But of course, this table needs to be generated by the compiler, and decoded (by ``get_exception_handler``) when an exception happens.

Exception table format
----------------------

The table is conceptually a list of records, each containing four variable-length integer fields (in a unique format, see below):

- start: start of ``try`` block, in code units from the start of the bytecode
- length: size of the ``try`` block, in code units
- target: start of the first instruction of the ``except`` or ``finally`` block, in code units from the start of the bytecode
- depth_and_lasti: the low bit gives the "lasti" flag, the remaining bits give the stack depth

The stack depth is used to clean up evaluation stack entries above this depth.
The "lasti" flag indicates whether, after stack cleanup, the instruction offset of the raising instruction should be pushed (as a ``PyLongObject *``).
For more information on the design, see :cpy-file:`Objects/exception_handling_notes.txt`.

Each varint is encoded as one or more bytes.
The high bit (bit 7) is reserved for random access -- it is set for the first varint of a record.
The second bit (bit 6) indicates whether this is the last byte or not -- it is set for all but the last bytes of a varint.
The low 6 bits (bits 0-5) are used for the integer value, in big-endian order.

To find the table entry (if any) for a given instruction offset, we can use bisection without decoding the whole table.
We bisect the raw bytes, at each probe finding the start of the record by scanning back for a byte with the high bit set, and then decode the first varint.
See ``get_exception_handler()`` in :cpy-file:`Python/ceval.c` for the exact code (like all bisection algorithms, the code is a bit subtle).
In 3.11, this mechanism has been replaced by a statically generated table, ``code->co_exceptiontable``,
which is described in detail in the `internals documentation
<https://github.com/python/cpython/blob/main/InternalDocs/exception_handling.md>`_.

The locations table
-------------------

Whenever an exception is raised, we add a traceback entry to the exception.
The ``tb_lineno`` field of a traceback entry must be set to the line number of the instruction that raised it.
The ``tb_lineno`` field of a traceback entry is (lazily) set to the line number of the instruction that raised it.
This field is computed from the locations table, ``co_linetable`` (this name is an understatement), using :c:func:`PyCode_Addr2Line`.
This table has an entry for every instruction rather than for every ``try`` block, so a compact format is very important.

Expand Down

0 comments on commit d2a63a9

Please sign in to comment.