Skip to content

gh-119786: remove part of devguide documentation which is duplicated in InternalDocs #1334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 31, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading