From 53a0857d5b0da303690733019b7d1de41ea58cf2 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:53:52 +0700 Subject: [PATCH] F11. Two tables on the side The line table and the exception table, both decoded by hand and both checked against the interpreter's own decoders. The angle is that the two are the same idea twice. A traceback needs to know which line and which columns an instruction came from, and a raise needs to know which handler covers an offset, and neither is needed while a program is going right. So both live beside the bytecode rather than in it, and nothing reads either until something has already gone wrong. The exception table decoder agrees with dis._parse_exception_table. The location table decoder agrees with co_positions() on 299 code objects out of four standard library modules. The zero cost claim is checked rather than asserted: the instructions inside a try are the same list as the same loop with no try around it, and SETUP_FINALLY and POP_BLOCK never reach the bytecode. One detail worth the trip: the two tables encode their integers with the six bit chunks in opposite orders, and the reason is that the exception table gets binary searched on its first number. Six diagrams, seven cells with version notes, and the lesson runs end to end in Pyodide. --- README.md | 1 + citations.lock.json | 50 + lessons/CLAIMS.md | 14 +- lessons/README.md | 1 + lessons/f11-two-tables-on-the-side/build.py | 539 ++++++ .../f11-two-tables-on-the-side/diagrams.py | 141 ++ .../how-a-raise-finds-its-handler.excalidraw | 580 +++++++ .../how-a-raise-finds-its-handler.svg | 1 + .../nothing-in-the-hot-path.excalidraw | 753 +++++++++ .../diagrams/nothing-in-the-hot-path.svg | 1 + .../diagrams/one-entry-decoded.excalidraw | 761 +++++++++ .../diagrams/one-entry-decoded.svg | 1 + .../diagrams/six-ways-to-say-where.excalidraw | 1457 +++++++++++++++++ .../diagrams/six-ways-to-say-where.svg | 1 + ...he-first-byte-says-what-follows.excalidraw | 403 +++++ .../the-first-byte-says-what-follows.svg | 1 + .../two-varints-in-one-file.excalidraw | 753 +++++++++ .../diagrams/two-varints-in-one-file.svg | 1 + lessons/f11-two-tables-on-the-side/f11.ipynb | 686 ++++++++ probes/pyodide/lessons.json | 74 +- probes/pyodide/lessons.md | 3 +- 21 files changed, 6212 insertions(+), 10 deletions(-) create mode 100644 lessons/f11-two-tables-on-the-side/build.py create mode 100644 lessons/f11-two-tables-on-the-side/diagrams.py create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.excalidraw create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.svg create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.excalidraw create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.svg create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.excalidraw create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.svg create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.excalidraw create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.svg create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.excalidraw create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.svg create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.excalidraw create mode 100644 lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.svg create mode 100644 lessons/f11-two-tables-on-the-side/f11.ipynb diff --git a/README.md b/README.md index 5c7e295..a7b8661 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ A lesson can also end with a boss fight, which is a problem the text does not so | F08 | [The optimizer](lessons/f08-the-optimizer/f08.ipynb) | Why 2 ** 64 is worked out at compile time and 2 ** 65 is not, the four size limits written down as constants, the leftover number that survives because slot zero might be a docstring, a jump deleted by copying the two instructions it pointed at, the line break that costs you a superinstruction, and how the compiler proves a local has a value | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f08-the-optimizer/f08.ipynb) | | F09 | [Two bytes at a time](lessons/f09-two-bytes-at-a-time/f09.ipynb) | Every byte of a two line function read by hand, the cache slots that are in the instruction stream but never run, why dis offsets go 0, 4, 6, 8, 20, how to rebuild a jump target from its argument, the boundary where one argument byte stops being enough, and the awful hack that sorts it out | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f09-two-bytes-at-a-time/f09.ipynb) | | F10 | [Inside a code object](lessons/f10-inside-a-code-object/f10.ipynb) | The record the compiler hands back, walked from a module down to a method, the seven slots behind three tuples that add up to eight, why a closure's first instruction runs before line one, every bit in co_flags, what equality compares and what it deliberately ignores, and what happens when you try to change one | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f10-inside-a-code-object/f10.ipynb) | +| F11 | [Two tables on the side](lessons/f11-two-tables-on-the-side/f11.ipynb) | The two blobs beside the bytecode, why wrapping a loop in a try changes not one instruction, the four numbers in an exception table entry and the marker bit that makes a variable length table binary searchable, the six forms a location entry can take, and two decoders written here that agree with the interpreter's | M3 | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f11-two-tables-on-the-side/f11.ipynb) | More are landing in order. [lessons/README.md](lessons/README.md) explains how one is put together and how to run them locally. diff --git a/citations.lock.json b/citations.lock.json index b09e66f..c82ad2e 100644 --- a/citations.lock.json +++ b/citations.lock.json @@ -70,6 +70,11 @@ "first_line": "#define CO_OPTIMIZED 0x0001", "lines": 36 }, + "Include/cpython/code.h:314-325@v3.15.0rc1": { + "digest": "69c2883f3fd31c09", + "first_line": "typedef enum _PyCodeLocationInfoKind {", + "lines": 12 + }, "Include/cpython/code.h:45-84@v3.15.0rc1": { "digest": "b8093c5db4f33759", "first_line": "#define _PyCode_DEF(SIZE) { \\", @@ -130,6 +135,21 @@ "first_line": "#define CO_FAST_ARG_POS (0x02) // pos-only, pos-or-kw, varargs", "lines": 8 }, + "Include/internal/pycore_code.h:394-403@v3.15.0rc1": { + "digest": "f19a30d3d1b1672b", + "first_line": "static inline unsigned char *", + "lines": 10 + }, + "Include/internal/pycore_code.h:405-416@v3.15.0rc1": { + "digest": "4258457082674f92", + "first_line": "static inline int", + "lines": 12 + }, + "Include/internal/pycore_code.h:432-437@v3.15.0rc1": { + "digest": "3969a1749fa282ab", + "first_line": "static inline int", + "lines": 6 + }, "Include/internal/pycore_flowgraph.h:27-31@v3.15.0rc1": { "digest": "5f318ca1d3438caa", "first_line": "struct _PyCfgBuilder* _PyCfg_FromInstructionSequence(_PyInstructionSequence *seq);", @@ -490,6 +510,11 @@ "first_line": "static int", "lines": 22 }, + "Objects/codeobject.c:1201-1225@v3.15.0rc1": { + "digest": "db7d492e583ccdfe", + "first_line": "static void", + "lines": 25 + }, "Objects/codeobject.c:2502-2541@v3.15.0rc1": { "digest": "83840c731b19be0c", "first_line": "code_richcompare(PyObject *self, PyObject *other, int op)", @@ -1335,6 +1360,21 @@ "first_line": "self->tok = _PyTokenizer_FromReadline(readline, encoding, 1, 1);", "lines": 1 }, + "Python/assemble.c:133-156@v3.15.0rc1": { + "digest": "4e437d03e06750b5", + "first_line": "assemble_emit_exception_table_entry(struct assembler *a, int start, int end,", + "lines": 24 + }, + "Python/assemble.c:158-190@v3.15.0rc1": { + "digest": "976fe9d268bf6d82", + "first_line": "assemble_exception_table(struct assembler *a, instr_sequence *instrs)", + "lines": 33 + }, + "Python/assemble.c:257-267@v3.15.0rc1": { + "digest": "70e139cd9b341390", + "first_line": "static void", + "lines": 11 + }, "Python/assemble.c:368-406@v3.15.0rc1": { "digest": "08bbae3c3ba3a552", "first_line": "static void", @@ -1475,6 +1515,16 @@ "first_line": "PyObject *", "lines": 30 }, + "Python/ceval.h:440-444@v3.15.0rc1": { + "digest": "ab7807756cb3110a", + "first_line": "static inline unsigned char *", + "lines": 5 + }, + "Python/ceval.h:457-490@v3.15.0rc1": { + "digest": "7b8f661400efec41", + "first_line": "static Py_NO_INLINE int", + "lines": 34 + }, "Python/ceval_macros.h:128-141@v3.15.0rc1": { "digest": "b032ce0706e98a42", "first_line": "#elif USE_COMPUTED_GOTOS", diff --git a/lessons/CLAIMS.md b/lessons/CLAIMS.md index f5c1222..b132133 100644 --- a/lessons/CLAIMS.md +++ b/lessons/CLAIMS.md @@ -12,7 +12,7 @@ header, what the allocator does with a freed block, the shape of the eval loop. marked with the reason, and a lesson is allowed at most 3 of them. The cap is the point. Without it the exception becomes the rule and this goes back to being a book. -229 claims across 26 lessons, 19 of them not observable from Python. +236 claims across 27 lessons, 19 of them not observable from Python. ## B01. Building CPython, and whether you need to @@ -159,6 +159,18 @@ Without it the exception becomes the rule and this goes back to being a book. | the same source compiled from two different filenames gives two equal code objects | [`f10-15`](f10-inside-a-code-object/f10.ipynb) | | replace gives you a new code object and leaves the original alone | [`f10-17`](f10-inside-a-code-object/f10.ipynb) | +## F11. Two tables on the side + +| Claim | Proved by | +| --- | --- | +| the instructions inside a try block are identical to the same code with no try around it | [`f11-07`](f11-two-tables-on-the-side/f11.ipynb) | +| an exception table entry decodes to start, size, target and a doubled depth | [`f11-13`](f11-two-tables-on-the-side/f11.ipynb) | +| a dozen lines of Python decode co_exceptiontable exactly as dis does | [`f11-16`](f11-two-tables-on-the-side/f11.ipynb) | +| the caret in a traceback is drawn from the column numbers in co_linetable | [`f11-20`](f11-two-tables-on-the-side/f11.ipynb) | +| some instructions in a normal function have no source location at all | [`f11-23`](f11-two-tables-on-the-side/f11.ipynb) | +| a hand written decoder reproduces co_positions() for every code object in a standard library module | [`f11-26`](f11-two-tables-on-the-side/f11.ipynb) | +| the two tables encode integers with their chunks in opposite orders | [`f11-31`](f11-two-tables-on-the-side/f11.ipynb) | + ## T01. One line, seven stages | Claim | Proved by | diff --git a/lessons/README.md b/lessons/README.md index e18236c..a6acf00 100644 --- a/lessons/README.md +++ b/lessons/README.md @@ -30,6 +30,7 @@ Each lesson is a notebook you can run. There is nothing to install and nothing t | [F08. The optimizer](f08-the-optimizer/f08.ipynb) | The pass that runs while the graph is in that shape, constant folding and the four numbers that stop it, the unused constant that slot zero protects, jumps removed by copying a short ending, two instructions merged into one when they fit, and the graph walk behind LOAD_FAST_CHECK | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f08-the-optimizer/f08.ipynb) | | [F09. Two bytes at a time](f09-two-bytes-at-a-time/f09.ipynb) | The assembler, reading the bytes of a small function one pair at a time, the cache slots that make the offsets skip, why a jump argument is counted from after the fetch, what an EXTENDED_ARG costs, and the loop that has to run twice because widening a jump moves its target | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f09-two-bytes-at-a-time/f09.ipynb) | | [F10. Inside a code object](f10-inside-a-code-object/f10.ipynb) | What compiling a file actually hands you, the code objects that live in other code objects' constants, the one array behind co_varnames and co_cellvars and co_freevars, the instructions that run before your first line, what co_flags remembers, and why two code objects can be equal without being the same one | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f10-inside-a-code-object/f10.ipynb) | +| [F11. Two tables on the side](f11-two-tables-on-the-side/f11.ipynb) | Why a try you never trip costs nothing, the exception table decoded byte by byte and checked against dis, how a raise binary searches a table whose entries are not the same length, the six shapes a source location can take, and a hand written line table decoder run against every code object in dis.py | [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f11-two-tables-on-the-side/f11.ipynb) | ## The three programs diff --git a/lessons/f11-two-tables-on-the-side/build.py b/lessons/f11-two-tables-on-the-side/build.py new file mode 100644 index 0000000..e4e46c8 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/build.py @@ -0,0 +1,539 @@ +#!/usr/bin/env python +"""F11. Two tables on the side. + +The eleventh lesson of the front end part, and the twenty fifth overall. F10 opened the code +object and named its two side tables without saying what is in them. This one decodes both, +byte by byte, and checks the answers against the interpreter's own. + +The angle is that the two tables are the same idea twice. A traceback needs to know which line +and which columns an instruction came from. A raise needs to know which handler covers an +offset. Neither is needed while a program is going right, so neither is allowed to cost +anything while it is. Both got moved out of the instruction stream and into a compressed blob +that nothing reads until something has already gone wrong. + +The payoff is that both blobs are short enough to decode in about thirty lines of Python, and +the reader can check their decoder against `co_positions()` and `dis._parse_exception_table` +rather than taking anybody's word for it. + +Run this file to regenerate the notebook, or `just build-lessons` to regenerate all of them. +`just lessons` checks that the committed notebook still matches this file. +""" + +from nbbuild import BANNER, Lesson +from nbdiagram import Diagrams + +lesson = Lesson("f11-two-tables-on-the-side", "f11") +badge = lesson.badge +cite = lesson.cite +term = lesson.term +figure = Diagrams("f11-two-tables-on-the-side").figure + + +lesson.md(f""" +# F11. Two tables on the side + +{badge} + +Write a `try` around a loop and the loop does not get any slower. The instructions inside the `try` are the same ones you get without it, in the same order, at the same offsets. + +Nothing in the bytecode says a `try` started or ended. What CPython has instead is a table saying which range of offsets each handler covers, and nothing reads it until something raises. That is zero cost exception handling. + +Source locations work the same way. Every instruction knows its line and columns, which is how a traceback underlines the failing part of an expression, and those four numbers per instruction live in a second blob beside the bytecode. + +Two tables, same idea. This lesson decodes both. + +{figure("nothing-in-the-hot-path", "the loop instructions on one side and the two side tables on the other")} +""") + + +lesson.md(""" +## About the source references + +Now and then this lesson points at CPython's own source, like this: `Include/cpython/code.h:314-325@v3.15.0rc1`. + +Read it as three parts: the file, the lines, and the release those line numbers belong to. Sometimes there is a fourth part after a `#`, which is the name of the thing those lines are inside. + +Every reference is a link, and every one is checked against the pinned source on each change, so a stale reference fails the build instead of sending you somewhere wrong. You never have to read any of it. The references are there so you can go deeper when you want to, and so you can check that this lesson is not making things up. + +## Setup + +Colab does not come with the small package these lessons use, so the next cell installs it. If you are running this from a checkout of the repository it is already installed and the cell does nothing. +""") + + +lesson.code(""" +import sys + +if sys.version_info < (3, 14): + print("This lesson needs CPython 3.14 or newer.") + print(f"This runtime is {sys.version.split()[0]}, and the cells below will not run on it.") +else: + try: + import pyxray + except ImportError: + %pip install -q "pyxray @ git+https://github.com/tamnd/cpython-internals@main#subdirectory=pyxray" + import pyxray +""") + + +lesson.md(""" +## Which Python is this + +Everything below was checked against the version this cell prints and against 3.14. Where the two disagree, the lesson says so. +""") + + +lesson.code( + """ +import pyxray + +pyxray.show() +""", + differs=BANNER, + quiet=True, +) + + +lesson.md(f""" +## The try that is not there + +Start with the claim, because it is easy to check and hard to believe. + +Take a loop. Wrap it in a `try` with an `except` after it. Then compare the instructions inside the covered range against the instructions of the same loop with no `try` around it at all. + +{lesson.claim("the instructions inside a try block are identical to the same code with no try around it")} +""") + + +lesson.code( + """ +import dis + +PLAIN = "def f(xs):\\n t = 0\\n for x in xs:\\n t += x\\n return t\\n" +TRIED = ( + "def f(xs):\\n t = 0\\n try:\\n for x in xs:\\n t += x\\n" + " except TypeError:\\n t = -1\\n return t\\n" +) + +plain = compile(PLAIN, "", "exec").co_consts[0] +tried = compile(TRIED, "", "exec").co_consts[0] + +covered = dis._parse_exception_table(tried)[0] +inside = [ + one.opname for one in dis.get_instructions(tried) if covered.start <= one.offset < covered.end +] +loop = [one.opname for one in dis.get_instructions(plain)][3:-2] + +print(f" the try covers bytes {covered.start} to {covered.end}, handler at {covered.target}") +print() +print(f" inside the try {len(inside)} instructions") +print(f" the plain loop {len(loop)} instructions") +print(f" the same list {inside == loop}") +print() +total = len(list(dis.get_instructions(tried))) +print(f" the try version has {total} instructions in total,") +print(f" and the {total - len(inside)} outside it are the setup, the return and the handler") +""", + varies="The offsets are 3.15's. On 3.14 the loop compiles two bytes shorter so every number here shifts down, and the two lists still match, which is the part that matters.", +) + + +lesson.md(""" +Two pseudo instructions used to be there. `SETUP_FINALLY` said "from here on, exceptions go to L1", and `POP_BLOCK` undid it. They still exist in the compiler, because the code generator finds them convenient to think in, and F08 watched the optimizer move them around. The assembler deletes both and writes the exception table instead, which is `Python/assemble.c:158-190@v3.15.0rc1#assemble_exception_table`. + +So neither one survives into the bytecode. Worth checking rather than believing. +""") + + +lesson.code( + """ +names = {one.opname for one in dis.get_instructions(tried)} + +for one in ("SETUP_FINALLY", "POP_BLOCK", "SETUP_CLEANUP", "SETUP_WITH"): + print(f" {one:16} in the bytecode: {one in names}") + +print() +print(f" and the whole exception table is {len(tried.co_exceptiontable)} bytes:") +print(f" {' '.join(f'{b:02x}' for b in tried.co_exceptiontable)}") +print(f" the plain version's table is {len(plain.co_exceptiontable)} bytes") +""", + varies="The table's bytes are 3.15's. On 3.14 the same function needs twelve bytes rather than sixteen, because it has one fewer handler entry.", +) + + +lesson.md(f""" +## Four numbers, and one of them is doubled + +An exception table entry is conceptually five things: where the covered range starts, where it ends, where the handler is, how deep the stack should be when the handler starts, and whether the offset of the failing instruction has to be pushed too. + +The last one is `lasti`, and it is there for re raising. At the end of a `finally` block an in flight exception has to be raised again and has to keep pointing at the instruction that first raised it, but by then the instruction pointer is somewhere inside the `finally`. So the original offset gets pushed on the stack and `RERAISE` puts it back. + +Five things get stored as four, because the size is always smaller than the end, so `start, size, target, depth` is cheaper than `start, end, target, depth`, and `depth` and `lasti` share a number as `depth * 2 + lasti`. That is `Python/assemble.c:133-156@v3.15.0rc1#assemble_emit_exception_table_entry`. + +{figure("one-entry-decoded", "the four bytes of one exception table entry, labelled")} + +Every number in the encoding counts code units rather than bytes, so everything doubles on the way out. + +{lesson.claim("an exception table entry decodes to start, size, target and a doubled depth")} +""") + + +lesson.code( + """ +table = tried.co_exceptiontable + +print(f" {' '.join(f'{b:02x}' for b in table)}") +print() +for i, b in enumerate(table[:4]): + starts = "yes" if b & 0x80 else "no" + more = "yes" if b & 0x40 else "no" + print(f" byte {i}: {b:3} {b:08b} starts: {starts:3} more: {more:3} value {b & 0x3F}") + +start, size, target, both = (b & 0x3F for b in table[:4]) + +print() +print(f" so start {start}, size {size}, target {target}, and depth and lasti packed into {both}") +print(f" which covers bytes {start * 2} to {(start + size) * 2}, handler at byte {target * 2}") +print(f" with the stack popped back to {both >> 1} and lasti {bool(both & 1)}") +""", + varies="These four bytes are 3.15's. On 3.14 they read 84 11 17 00, which is start 4, size 17, target 23, and the walk through them is the same walk.", +) + + +lesson.md(f""" +That is the whole format. The top bit marks the first byte of an entry, bit 6 says another byte follows, and the low six bits carry the value. Which means the entire table can be decoded in about a dozen lines. + +Here is a decoder, and next to it what `dis` says, so there is nothing to take on trust. + +{lesson.claim("a dozen lines of Python decode co_exceptiontable exactly as dis does")} +""") + + +lesson.code( + """ +import dis + + +def handlers(table): + \"\"\"Every entry in a co_exceptiontable, as byte offsets rather than code units.\"\"\" + + def number(at): + byte = table[at] + value = byte & 0x3F + while byte & 0x40: + at += 1 + byte = table[at] + value = (value << 6) | (byte & 0x3F) + return value, at + 1 + + at = 0 + while at < len(table): + start, at = number(at) + size, at = number(at) + target, at = number(at) + both, at = number(at) + yield start * 2, (start + size) * 2, target * 2, both >> 1, bool(both & 1) + + +def guarded(items): + total = 0 + try: + for one in items: + total = total + one + except TypeError: + total = -1 + finally: + print(total) + return total + + +mine = list(handlers(guarded.__code__.co_exceptiontable)) +theirs = [tuple(one) for one in dis._parse_exception_table(guarded.__code__)] + +print(" start end target depth lasti") +for one in mine: + print(f" {one[0]:5} {one[1]:3} {one[2]:6} {one[3]:5} {one[4]}") +print() +print(f" {len(mine)} entries in {len(guarded.__code__.co_exceptiontable)} bytes") +print(f" and dis agrees on every one of them: {mine == theirs}") +""", + varies="The offsets are 3.15's and 3.14 puts them a few bytes lower. The line that matters is the last one, and it says True on both.", +) + + +lesson.md(f""" +## Why the top bit is set + +The marker bit looks like a small thing and it is the reason the format was chosen. + +When something raises, the interpreter has an offset and needs the entry covering it. Entries vary in size, so you cannot index into the table. But because every entry starts with a byte that has the top bit set, and no other byte in an entry does, you can land anywhere in the middle of the table and walk backwards until you find one. That is `Python/ceval.h:440-444@v3.15.0rc1#scan_back_to_entry_start`, four lines long. + +Which means binary search works on a variable length table. Jump to the middle, walk back to the nearest entry boundary, read its start offset, and go left or right. `Python/ceval.h:457-490@v3.15.0rc1#get_exception_handler` does exactly that, and falls back to a straight scan once the range is under forty bytes, which is where a binary search stops being worth the trouble. + +{figure("how-a-raise-finds-its-handler", "raise, look up the offset, unwind the stack, jump to the handler")} + +The depth in the entry is what the second step needs. When a handler starts, the stack has to look the way it looked when the `try` began, whatever the failing code left on it. Rather than tracking that at run time, the compiler works it out once and writes it down. +""") + + +lesson.md(f""" +## Every instruction knows where it came from + +Now the other table. This one is bigger and the format is more interesting. + +`co_positions()` gives four numbers per instruction: start line, end line, start column, end column. That is what lets a traceback underline the failing part of an expression instead of the whole line. Here is that underline, rebuilt from the table by hand rather than printed by `traceback`, so you can see exactly where it comes from. + +{lesson.claim("the caret in a traceback is drawn from the column numbers in co_linetable")} +""") + + +lesson.code( + """ +SOURCE = \"\"\"def totals(first, second): + return first["count"] + second["count"] + +totals({"count": 1}, {"total": 2}) +\"\"\" + +lines = SOURCE.splitlines() + +try: + exec(compile(SOURCE, "example.py", "exec"), {}) +except KeyError as problem: + deepest = problem.__traceback__ + while deepest.tb_next: + deepest = deepest.tb_next + inner = deepest.tb_frame.f_code + line, end_line, start, end = list(inner.co_positions())[deepest.tb_lasti // 2] + print(f" it failed at instruction {deepest.tb_lasti // 2}, which the table puts at") + print(f" line {line}, columns {start} to {end}") + print() + print(" ", lines[line - 1]) + print(" ", " " * start + "^" * (end - start)) +""", + varies="3.14 reaches the failure one instruction earlier, so the instruction number differs. The line and the columns it reports, and so the caret, are the same on both.", +) + + +lesson.md(f""" +## Six ways to say where + +Storing four numbers for every instruction plainly would cost sixteen bytes each, which for a small function is more than the bytecode. So the format has six shapes and the assembler picks the smallest one that fits. + +Every entry begins with one byte carrying three things: a marker bit, which of the six forms this is, and how many code units the entry covers. `Include/internal/pycore_code.h:432-437@v3.15.0rc1#write_location_entry_start` is the one line that builds it. + +{figure("the-first-byte-says-what-follows", "the three fields packed into the first byte of a location entry")} + +The six forms are `Include/cpython/code.h:314-325@v3.15.0rc1`, and the reason there are six is that almost every instruction is boring. Same line as the one before, columns under eighty, span under sixteen characters. That case is two bytes. + +{figure("six-ways-to-say-where", "the six location entry forms and what each one stores")} + +Form 15 is the odd one. Some instructions did not come from your source at all, and they get no location. In a function with a `try` in it they are the exception plumbing, the instructions that save the in flight exception and re raise it, which no line you wrote asked for. + +{lesson.claim("some instructions in a normal function have no source location at all")} +""") + + +lesson.code( + """ +import dis + +where = list(guarded.__code__.co_positions()) +nowhere = [one.opname for one in dis.get_instructions(guarded) if where[one.offset // 2][0] is None] + +print(f" {len(list(dis.get_instructions(guarded)))} instructions in guarded") +print(f" {len(nowhere)} of them come from nowhere, and they are {sorted(set(nowhere))}") +print() +print(f" co_code {len(guarded.__code__.co_code):4} bytes") +print(f" co_linetable {len(guarded.__code__.co_linetable):4} bytes") +print(f" co_exceptiontable {len(guarded.__code__.co_exceptiontable):4} bytes") +""", + varies="The counts and the byte sizes are 3.15's. 3.14 compiles the same function a little smaller, and the instructions with no location are the same exception plumbing either way.", +) + + +lesson.md(f""" +## Decoding it + +Thirty lines. The only fiddly parts are the two variable length integers and one off by one. + +The off by one is that the long form stores each column plus one, so that zero can mean "no column here". `Python/assemble.c:257-267@v3.15.0rc1#write_location_info_long_form` adds the one on the way in and `Objects/codeobject.c:1201-1225@v3.15.0rc1#advance_with_locations` takes it off on the way out. The internal documentation does not mention it, which is a reasonable reminder that the source is the specification. + +{lesson.claim("a hand written decoder reproduces co_positions() for every code object in a standard library module")} +""") + + +lesson.code(""" +def positions(code): + \"\"\"Every instruction's source location, decoded out of co_linetable by hand.\"\"\" + table = code.co_linetable + line = code.co_firstlineno + at = 0 + + def varint(): + nonlocal at + value = shift = 0 + while True: + chunk = table[at] + at += 1 + value |= (chunk & 63) << shift + shift += 6 + if not chunk & 64: + return value + + def svarint(): + value = varint() + return -(value >> 1) if value & 1 else value >> 1 + + while at < len(table): + first = table[at] + at += 1 + kind = (first >> 3) & 15 + length = (first & 7) + 1 + if kind == 15: + found = (None, None, None, None) + elif kind == 13: + line += svarint() + found = (line, line, None, None) + elif kind == 14: + line += svarint() + end_line = line + varint() + start, end = varint() - 1, varint() - 1 + found = (line, end_line, start if start >= 0 else None, end if end >= 0 else None) + elif kind >= 10: + line += kind - 10 + found = (line, line, table[at], table[at + 1]) + at += 2 + else: + second = table[at] + at += 1 + start = kind * 8 + ((second >> 4) & 7) + found = (line, line, start, start + (second & 15)) + yield from [found] * length +""") + + +lesson.md(""" +Now run it against something. Not one hand picked function, because that proves nothing, but every code object in a handful of standard library modules, nested functions and comprehensions and all. +""") + + +lesson.code( + """ +import argparse +import dataclasses +import json.decoder +import types + + +def everything(code): + \"\"\"This code object and every one nested inside it.\"\"\" + yield code + for one in code.co_consts: + if isinstance(one, types.CodeType): + yield from everything(one) + + +def written_in(module): + \"\"\"Every code object belonging to functions and methods defined in this module.\"\"\" + for one in vars(module).values(): + if isinstance(one, types.FunctionType) and one.__module__ == module.__name__: + yield from everything(one.__code__) + elif isinstance(one, type) and one.__module__ == module.__name__: + for other in vars(one).values(): + if isinstance(other, types.FunctionType): + yield from everything(other.__code__) + + +checked = wrong = 0 +for module in (argparse, dataclasses, dis, json.decoder): + for one in written_in(module): + checked += 1 + if list(positions(one)) != list(one.co_positions()): + wrong += 1 + print(f" mismatch in {module.__name__}.{one.co_name}") + +print(f" {checked} code objects out of four standard library modules") +print(f" {wrong} of them decoded differently from co_positions()") +""", + varies="How many code objects those four modules hold depends on the version, because the modules themselves change. The number that has to be zero is zero on both.", +) + + +lesson.md(f""" +## Two varints, opposite ways round + +One last detail, and it is the kind of thing that looks like sloppiness until you see why. + +Both tables encode numbers six bits to a byte with bit 6 meaning "another byte follows". They put the chunks in opposite orders. The line table writes the least significant chunk first, in `Include/internal/pycore_code.h:405-416@v3.15.0rc1#write_varint`. The exception table reads the most significant chunk first, in `Include/internal/pycore_code.h:394-403@v3.15.0rc1#parse_varint`. + +{figure("two-varints-in-one-file", "the two variable length integer encodings side by side")} + +The reason is the binary search. The exception table gets searched on the start offset of each entry, and reading the leading chunk of a most significant first number gives you the big end straight away. The line table is never searched that way, it is walked from the beginning, so it uses whichever order is easier to write. + +{lesson.claim("the two tables encode integers with their chunks in opposite orders")} +""") + + +lesson.code(""" +def as_exception_table(value): + \"\"\"How the exception table would write this number: biggest chunk first.\"\"\" + chunks = [] + while value >= 64: + chunks.append(value & 63) + value >>= 6 + chunks.append(value) + chunks.reverse() + out = [one | 0x40 for one in chunks[:-1]] + [chunks[-1]] + out[0] |= 0x80 + return out + + +def as_line_table(value): + \"\"\"How the line table would write it: smallest chunk first.\"\"\" + out = [] + while value >= 64: + out.append(64 | (value & 63)) + value >>= 6 + out.append(value) + return out + + +for number in (5, 100, 4000, 100000): + one = " ".join(f"{b:02x}" for b in as_exception_table(number)) + other = " ".join(f"{b:02x}" for b in as_line_table(number)) + print(f" {number:6} exception table {one:14} line table {other}") + +print() +print(" same number, same six bit chunks, read from opposite ends") +""") + + +lesson.md(""" +## Try it yourself + +Three things to poke at. + +Write a function with a `try` inside a `try` inside a loop, and print its exception table with the decoder above. The nesting shows up as overlapping ranges, and the depth column is what tells the handlers apart. + +Take a function with a long expression spread over several lines and print `co_positions()` next to `co_lines()`. The second is smaller and older and only gives you line numbers. Work out from the entry forms why. + +Compile the same function twice, once with the body all on one line and once spread over ten, and compare the length of `co_linetable`. The bytecode is identical. The table is not, and the difference is entirely which entry forms the assembler could get away with. + +## What just happened + +A `try` costs nothing when nothing raises, because nothing about it is in the bytecode. The pseudo instructions the compiler used are deleted by the assembler, and what replaces them is a table of ranges. + +That table holds start, size, target and a number that is the stack depth doubled plus a re raise flag, all in code units, all as six bit chunks. The top bit of the first byte of each entry is what makes a variable length table binary searchable, because you can land anywhere and walk backwards to a boundary. + +The line table is the same idea for source locations. Four numbers per instruction, six entry forms, and the assembler picks the smallest that fits. It is what draws the caret under the failing half of an expression in a traceback. + +Both are decodable in about thirty lines of Python, and both decoders agree with the interpreter, which is the only evidence worth having. + +## What is next + +F12 is marshal, which is how all of this gets written to a `.pyc` file and read back. It is the last lesson of the front end, and it closes the loop: source text in at F01, bytes on disk at F12, and the same code object at both ends. +""") + + +raise SystemExit(lesson.save()) diff --git a/lessons/f11-two-tables-on-the-side/diagrams.py b/lessons/f11-two-tables-on-the-side/diagrams.py new file mode 100644 index 0000000..5c84fb1 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +"""The diagrams for F11, the line table and the exception table. + +Each scene is written out twice, as an editable `.excalidraw` and as the `.svg` the lesson +embeds. Run this file to regenerate them, or `just build-diagrams` for every lesson. + +The one carrying the lesson is `nothing-in-the-hot-path`. Both tables exist for the same +reason: something that is needed only when a program goes wrong should cost nothing while it +is going right. So both live beside the bytecode rather than in it, and both are read only by +code that has already stopped running your instructions. +""" + +from nbdiagram import Gallery, figures + +gallery = Gallery("f11-two-tables-on-the-side") + +gallery.add( + figures.compare( + "nothing-in-the-hot-path", + ( + "in co_code, runs every time", + [ + "the loop that adds the numbers up", + "and that is the whole list", + "no instruction marks the try", + "no instruction unmarks it", + ], + ), + ( + "beside co_code, read on the way out", + [ + "co_exceptiontable, range to handler", + "co_linetable, line and columns", + "looked at when something raises", + "and when a debugger asks", + ], + ), + title="Why a try you never trip costs nothing", + verdict="The instructions inside a try are the ones you get without it.", + verdict_tone="focus", + ) +) + + +gallery.add( + figures.stack( + "the-first-byte-says-what-follows", + [ + "bit 7 set, so this byte starts an entry", + "bits 3 to 6, which of the six forms this is", + "bits 0 to 2, how many code units it covers, less one", + "then zero or more bytes with bit 7 clear", + ], + title="One byte at the front of every location entry, and it is doing three jobs", + note="The top bit is what lets you land in the middle of the table and walk backwards to a boundary.", + ) +) + + +gallery.add( + figures.table( + "six-ways-to-say-where", + ["code", "form", "what it stores", "bytes"], + [ + ["0 to 9", "short", "the column, in the code and one more byte", "2"], + ["10 to 12", "one line", "a line delta of 0, 1 or 2, and two columns", "3"], + ["13", "no columns", "a signed line delta, nothing else", "2"], + ["14", "long", "signed line delta, end line, both columns", "up to 25"], + ["15", "none", "nothing at all, this instruction is from nowhere", "1"], + ], + title="The compiler picks the smallest form that fits", + caption="Most instructions are on one line in the first eighty columns, which is what the two byte short form is for.", + tones=["focus", "quiet", "quiet", "quiet", "focus"], + ) +) + + +gallery.add( + figures.spans( + "one-entry-decoded", + "85 12 19 00", + [ + (0, 2, "start 5, top bit set"), + (3, 5, "size 18"), + (6, 8, "target 25"), + (9, 11, "depth 0, lasti off"), + ], + title="One entry, four bytes, and every number in code units", + caption="That covers bytes 10 to 46, puts the handler at byte 50, and pops the stack back to nothing.", + ) +) + + +gallery.add( + figures.flow( + "how-a-raise-finds-its-handler", + [ + "something raises at some offset", + "look that offset up in co_exceptiontable", + "pop the stack back to the depth in the entry", + "jump to the target and carry on", + ], + title="What happens between the raise and the except", + labels=[ + "binary search, even though entries vary in size", + "which is why the depth is in the table", + ], + tones=["input", "focus", "intermediate", "durable"], + ) +) + + +gallery.add( + figures.compare( + "two-varints-in-one-file", + ( + "the exception table", + [ + "most significant chunk first", + "bit 6 means another byte follows", + "read with parse_varint", + "so a prefix is already the big part", + ], + ), + ( + "the line table", + [ + "least significant chunk first", + "bit 6 means another byte follows", + "written with write_varint", + "so it is written as it is computed", + ], + ), + title="Six bits to a byte in both, and the chunks go opposite ways", + verdict="Not an accident. The exception table is binary searched on its first number, and comparing the leading chunk first is what makes that cheap.", + verdict_tone="focus", + ) +) + + +raise SystemExit(gallery.save()) diff --git a/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.excalidraw b/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.excalidraw new file mode 100644 index 0000000..8b484dc --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.excalidraw @@ -0,0 +1,580 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "f62f62aaabf1c861f67d", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 627.4799999999999, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "What happens between the raise and the except", + "originalText": "What happens between the raise and the except", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2a5608a1d66126cf0ac7", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 519.5, + "height": 70.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "5e38d5b2ebff974aee81", + "type": "text" + }, + { + "id": "bd8da45d4bae6e5c166d", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "5e38d5b2ebff974aee81", + "type": "text", + "x": 16.0, + "y": 72.5, + "width": 487.5, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "something raises at some offset", + "originalText": "something raises at some offset", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "2a5608a1d66126cf0ac7", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "e7e622744658d3f87b75", + "type": "rectangle", + "x": 0.0, + "y": 180.0, + "width": 519.5, + "height": 70.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "ef109ce876eaedf61f22", + "type": "text" + }, + { + "id": "bd8da45d4bae6e5c166d", + "type": "arrow" + }, + { + "id": "bc8b6218c1e28f6045e7", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "ef109ce876eaedf61f22", + "type": "text", + "x": 16.0, + "y": 202.5, + "width": 487.5, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "look that offset up in co_exceptiontable", + "originalText": "look that offset up in co_exceptiontable", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "e7e622744658d3f87b75", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "0072ffe008f38a30a9bf", + "type": "rectangle", + "x": 0.0, + "y": 310.0, + "width": 519.5, + "height": 70.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "2d6f0ae26e25e7ba4808", + "type": "text" + }, + { + "id": "bc8b6218c1e28f6045e7", + "type": "arrow" + }, + { + "id": "dac064705c5bf63e5421", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "2d6f0ae26e25e7ba4808", + "type": "text", + "x": 16.0, + "y": 332.5, + "width": 487.5, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "pop the stack back to the depth in the entry", + "originalText": "pop the stack back to the depth in the entry", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "0072ffe008f38a30a9bf", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "4c4cdcd781b0e48e02a7", + "type": "rectangle", + "x": 0.0, + "y": 440.0, + "width": 519.5, + "height": 70.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "7af8a7b5c3e299bc4e50", + "type": "text" + }, + { + "id": "dac064705c5bf63e5421", + "type": "arrow" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "7af8a7b5c3e299bc4e50", + "type": "text", + "x": 16.0, + "y": 462.5, + "width": 487.5, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "jump to the target and carry on", + "originalText": "jump to the target and carry on", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "4c4cdcd781b0e48e02a7", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "bd8da45d4bae6e5c166d", + "type": "arrow", + "x": 259.75, + "y": 120.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "2a5608a1d66126cf0ac7", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "e7e622744658d3f87b75", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "5913984c2f6472c7ea4a", + "type": "text", + "x": 271.75, + "y": 140.0, + "width": 411.59999999999997, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "binary search, even though entries vary in size", + "originalText": "binary search, even though entries vary in size", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "bc8b6218c1e28f6045e7", + "type": "arrow", + "x": 259.75, + "y": 250.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "e7e622744658d3f87b75", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "0072ffe008f38a30a9bf", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "4ad06708a6069b3d5979", + "type": "text", + "x": 271.75, + "y": 270.0, + "width": 343.91999999999996, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "which is why the depth is in the table", + "originalText": "which is why the depth is in the table", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "dac064705c5bf63e5421", + "type": "arrow", + "x": 259.75, + "y": 380.0, + "width": 0.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0.0, + 60.0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "0072ffe008f38a30a9bf", + "focus": 0, + "gap": 4 + }, + "endBinding": { + "elementId": "4c4cdcd781b0e48e02a7", + "focus": 0, + "gap": 4 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.svg b/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.svg new file mode 100644 index 0000000..6c133b8 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.svg @@ -0,0 +1 @@ +What happens between the raise and the exceptsomething raises at some offsetlook that offset up in co_exceptiontablepop the stack back to the depth in the entryjump to the target and carry onbinary search, even though entries vary in sizewhich is why the depth is in the table diff --git a/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.excalidraw b/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.excalidraw new file mode 100644 index 0000000..4de7d33 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.excalidraw @@ -0,0 +1,753 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "294649bb236b85c83579", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 507.24, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Why a try you never trip costs nothing", + "originalText": "Why a try you never trip costs nothing", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "c094e48b083da1dfe219", + "type": "text", + "x": 73.89999999999998, + "y": 50.0, + "width": 304.20000000000005, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "in co_code, runs every time", + "originalText": "in co_code, runs every time", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "33e8f389fd7248f564ec", + "type": "rectangle", + "x": 0.0, + "y": 87.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "9534097bfb3a5f68507b", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "9534097bfb3a5f68507b", + "type": "text", + "x": 16.0, + "y": 103.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "the loop that adds the numbers up", + "originalText": "the loop that adds the numbers up", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "33e8f389fd7248f564ec", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "2ef3aaec2a21f9689b98", + "type": "rectangle", + "x": 0.0, + "y": 147.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "5dd82c731fe5aa7e34a0", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "5dd82c731fe5aa7e34a0", + "type": "text", + "x": 16.0, + "y": 163.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and that is the whole list", + "originalText": "and that is the whole list", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "2ef3aaec2a21f9689b98", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b2f28234de59a3313af7", + "type": "rectangle", + "x": 0.0, + "y": 207.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "6ce8c9ae56c1ecb2e125", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "6ce8c9ae56c1ecb2e125", + "type": "text", + "x": 16.0, + "y": 223.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "no instruction marks the try", + "originalText": "no instruction marks the try", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "b2f28234de59a3313af7", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "1d27bb80c618180f2f89", + "type": "rectangle", + "x": 0.0, + "y": 267.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "5e346df197bff71e7efa", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "5e346df197bff71e7efa", + "type": "text", + "x": 16.0, + "y": 283.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "no instruction unmarks it", + "originalText": "no instruction unmarks it", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "1d27bb80c618180f2f89", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3d4922895d8783a340bc", + "type": "text", + "x": 535.65, + "y": 50.0, + "width": 404.7, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "beside co_code, read on the way out", + "originalText": "beside co_code, read on the way out", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "718cc383bdba34b77ccf", + "type": "rectangle", + "x": 512.0, + "y": 87.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "da4a51ba9ff782df8df2", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "da4a51ba9ff782df8df2", + "type": "text", + "x": 528.0, + "y": 103.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_exceptiontable, range to handler", + "originalText": "co_exceptiontable, range to handler", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "718cc383bdba34b77ccf", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "2e5c553b6df011e6948f", + "type": "rectangle", + "x": 512.0, + "y": 147.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "6d724dcf2d6c5639816d", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "6d724dcf2d6c5639816d", + "type": "text", + "x": 528.0, + "y": 163.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "co_linetable, line and columns", + "originalText": "co_linetable, line and columns", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "2e5c553b6df011e6948f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "06ffac6ebc10eb3eaf6c", + "type": "rectangle", + "x": 512.0, + "y": 207.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "37bee7b19193c131e441", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "37bee7b19193c131e441", + "type": "text", + "x": 528.0, + "y": 223.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "looked at when something raises", + "originalText": "looked at when something raises", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "06ffac6ebc10eb3eaf6c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b7779dd84ca28304ef5e", + "type": "rectangle", + "x": 512.0, + "y": 267.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "4949d3db26523429bf23", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "4949d3db26523429bf23", + "type": "text", + "x": 528.0, + "y": 283.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "and when a debugger asks", + "originalText": "and when a debugger asks", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "b7779dd84ca28304ef5e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "6ec337e9e53bafd24fe3", + "type": "rectangle", + "x": 0.0, + "y": 344.0, + "width": 964.0, + "height": 60.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "544ab1e5dba903664864", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "544ab1e5dba903664864", + "type": "text", + "x": 16.0, + "y": 361.5, + "width": 932.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "The instructions inside a try are the ones you get without it.", + "originalText": "The instructions inside a try are the ones you get without it.", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "6ec337e9e53bafd24fe3", + "lineHeight": 1.25, + "autoResize": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.svg b/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.svg new file mode 100644 index 0000000..926bada --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.svg @@ -0,0 +1 @@ +Why a try you never trip costs nothingin co_code, runs every timethe loop that adds the numbers upand that is the whole listno instruction marks the tryno instruction unmarks itbeside co_code, read on the way outco_exceptiontable, range to handlerco_linetable, line and columnslooked at when something raisesand when a debugger asksThe instructions inside a try are the ones you get without it. diff --git a/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.excalidraw b/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.excalidraw new file mode 100644 index 0000000..bfeaed4 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.excalidraw @@ -0,0 +1,761 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "dfddf84099b4068653dd", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 707.04, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "One entry, four bytes, and every number in code units", + "originalText": "One entry, four bytes, and every number in code units", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "72c458e75dd4773fa9f5", + "type": "text", + "x": 0.0, + "y": 50.0, + "width": 132.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": { + "textLength": 132.0 + }, + "text": "85 12 19 00", + "originalText": "85 12 19 00", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "f95d58985d8e32c2e2c4", + "type": "line", + "x": 0.0, + "y": 81.0, + "width": 195.2, + "height": 72.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ], + [ + 195.2, + 72.0 + ], + [ + 0.0, + 72.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "836a8ddc5ea9c9b439e0", + "type": "line", + "x": 36.0, + "y": 81.0, + "width": 262.79999999999995, + "height": 72.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ], + [ + 262.79999999999995, + 72.0 + ], + [ + 167.2, + 72.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "d27f337bfb577db88da9", + "type": "line", + "x": 72.0, + "y": 81.0, + "width": 342.4, + "height": 72.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ], + [ + 342.4, + 72.0 + ], + [ + 234.79999999999995, + 72.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "c78a371affe97a481ade", + "type": "line", + "x": 108.0, + "y": 81.0, + "width": 494.0, + "height": 72.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 40, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ], + [ + 494.0, + 72.0 + ], + [ + 314.4, + 72.0 + ], + [ + 0.0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "e322b93c3bc100e66615", + "type": "line", + "x": 0.0, + "y": 81.0, + "width": 24.0, + "height": 0.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "d450932b0f7f0ea64741", + "type": "line", + "x": 36.0, + "y": 81.0, + "width": 24.0, + "height": 0.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "afc3a6e18c37345fbeb8", + "type": "line", + "x": 72.0, + "y": 81.0, + "width": 24.0, + "height": 0.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "92f50c5e27d4b22d9f22", + "type": "line", + "x": 108.0, + "y": 81.0, + "width": 24.0, + "height": 0.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0.0, + 0.0 + ], + [ + 24.0, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "9afd439dce7f3d5a1a3f", + "type": "rectangle", + "x": 0.0, + "y": 153.0, + "width": 195.2, + "height": 52.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "867dbdf1690fcc609fb5", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "867dbdf1690fcc609fb5", + "type": "text", + "x": 16.0, + "y": 169.0, + "width": 163.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "start 5, top bit set", + "originalText": "start 5, top bit set", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "9afd439dce7f3d5a1a3f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "33797eb707d003dff289", + "type": "rectangle", + "x": 203.2, + "y": 153.0, + "width": 95.6, + "height": 52.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "60a7accf8965374b58bb", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "60a7accf8965374b58bb", + "type": "text", + "x": 219.2, + "y": 169.0, + "width": 63.599999999999994, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "size 18", + "originalText": "size 18", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "33797eb707d003dff289", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "94ea9fbb2c811843d5ba", + "type": "rectangle", + "x": 306.79999999999995, + "y": 153.0, + "width": 107.6, + "height": 52.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "8d3c5aeda2b497c45b46", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "8d3c5aeda2b497c45b46", + "type": "text", + "x": 322.79999999999995, + "y": 169.0, + "width": 75.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "target 25", + "originalText": "target 25", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "94ea9fbb2c811843d5ba", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "73316dc4e903297a354c", + "type": "rectangle", + "x": 422.4, + "y": 153.0, + "width": 179.6, + "height": 52.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "fc4d90febfa119cec4ca", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "fc4d90febfa119cec4ca", + "type": "text", + "x": 438.4, + "y": 169.0, + "width": 147.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "depth 0, lasti off", + "originalText": "depth 0, lasti off", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "73316dc4e903297a354c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "36ad4445382c231f55ca", + "type": "text", + "x": 0.0, + "y": 225.0, + "width": 818.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "That covers bytes 10 to 46, puts the handler at byte 50, and pops the stack back to nothing.", + "originalText": "That covers bytes 10 to 46, puts the handler at byte 50, and pops the stack back to nothing.", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.svg b/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.svg new file mode 100644 index 0000000..575964f --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.svg @@ -0,0 +1 @@ +One entry, four bytes, and every number in code units85 12 19 00start 5, top bit setsize 18target 25depth 0, lasti offThat covers bytes 10 to 46, puts the handler at byte 50, and pops the stack back to nothing. diff --git a/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.excalidraw b/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.excalidraw new file mode 100644 index 0000000..aa0e45c --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.excalidraw @@ -0,0 +1,1457 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "81ca7b35ce8bc2936be7", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 607.3199999999999, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "The compiler picks the smallest form that fits", + "originalText": "The compiler picks the smallest form that fits", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d3e40e5fe3cfd46490ce", + "type": "text", + "x": 14.0, + "y": 50.0, + "width": 38.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "code", + "originalText": "code", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "89e1e3a4e5320eddc708", + "type": "text", + "x": 118.8, + "y": 50.0, + "width": 36.96, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "form", + "originalText": "form", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7c5babdbd93797a5ded0", + "type": "text", + "x": 242.8, + "y": 50.0, + "width": 122.16, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "what it stores", + "originalText": "what it stores", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "929cd8500fc3e037847a", + "type": "text", + "x": 731.5999999999999, + "y": 50.0, + "width": 44.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "bytes", + "originalText": "bytes", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "dfe3c2552715bfafe103", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 822.4, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "d381ae313408f0d4b98c", + "type": "line", + "x": 0.0, + "y": 76.0, + "width": 822.4, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ], + [ + 822.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "c555e7c4c7cfbdcbf0af", + "type": "text", + "x": 14.0, + "y": 86.0, + "width": 57.599999999999994, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "0 to 9", + "originalText": "0 to 9", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "47aee6c9f5b5d0f0af1d", + "type": "text", + "x": 118.8, + "y": 86.0, + "width": 48.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "short", + "originalText": "short", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "bc08b4cd6dfff0cee578", + "type": "text", + "x": 242.8, + "y": 86.0, + "width": 393.59999999999997, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the column, in the code and one more byte", + "originalText": "the column, in the code and one more byte", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "57fcb276e47c5d05b6c4", + "type": "text", + "x": 731.5999999999999, + "y": 86.0, + "width": 9.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "26046b826dd1b44ca50d", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 822.4, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "4d126964b2265e2dc6ac", + "type": "line", + "x": 0.0, + "y": 116.0, + "width": 822.4, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ], + [ + 822.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "737dc18e4062e62fe6eb", + "type": "text", + "x": 14.0, + "y": 126.0, + "width": 76.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "10 to 12", + "originalText": "10 to 12", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "969b6b117d4a41772765", + "type": "text", + "x": 118.8, + "y": 126.0, + "width": 76.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "one line", + "originalText": "one line", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "b04ba2766deef09b91cb", + "type": "text", + "x": 242.8, + "y": 126.0, + "width": 403.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a line delta of 0, 1 or 2, and two columns", + "originalText": "a line delta of 0, 1 or 2, and two columns", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "11b5a7e6f8c18738f31c", + "type": "text", + "x": 731.5999999999999, + "y": 126.0, + "width": 9.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "3", + "originalText": "3", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "245994dc57a58ee7fcfd", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 822.4, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "aa356b7faad64fbdbb05", + "type": "line", + "x": 0.0, + "y": 156.0, + "width": 822.4, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ], + [ + 822.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "65b2d73ece2dda2962f1", + "type": "text", + "x": 14.0, + "y": 166.0, + "width": 19.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "13", + "originalText": "13", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "259c50d5d862261994ab", + "type": "text", + "x": 118.8, + "y": 166.0, + "width": 96.0, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "no columns", + "originalText": "no columns", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "df6de7ddd4cebf7aa7b8", + "type": "text", + "x": 242.8, + "y": 166.0, + "width": 316.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "a signed line delta, nothing else", + "originalText": "a signed line delta, nothing else", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2c81af7e67e9460cec65", + "type": "text", + "x": 731.5999999999999, + "y": 166.0, + "width": 9.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "2", + "originalText": "2", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "108b314a0512a843bb8c", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 822.4, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "a759abfe768ca535159a", + "type": "line", + "x": 0.0, + "y": 196.0, + "width": 822.4, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ], + [ + 822.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "162b3ad916d74b84df48", + "type": "text", + "x": 14.0, + "y": 206.0, + "width": 19.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "14", + "originalText": "14", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "e4a777a7f9411cb161ca", + "type": "text", + "x": 118.8, + "y": 206.0, + "width": 38.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "long", + "originalText": "long", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "a647f1a56c9c4ca05c70", + "type": "text", + "x": 242.8, + "y": 206.0, + "width": 393.59999999999997, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "signed line delta, end line, both columns", + "originalText": "signed line delta, end line, both columns", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "7c58df75d995f4a98c64", + "type": "text", + "x": 731.5999999999999, + "y": 206.0, + "width": 76.8, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "up to 25", + "originalText": "up to 25", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "31dbb507993b3bbe7a48", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 822.4, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "5732d7137d954b56aeb5", + "type": "line", + "x": 0.0, + "y": 236.0, + "width": 822.4, + "height": 40.0, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ], + [ + 822.4, + 40.0 + ], + [ + 0, + 40.0 + ], + [ + 0, + 0.0 + ] + ], + "lastCommittedPoint": null, + "polygon": true + }, + { + "id": "452e50cf409a09387c50", + "type": "text", + "x": 14.0, + "y": 246.0, + "width": 19.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "15", + "originalText": "15", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "56ea3019fb83c33a969a", + "type": "text", + "x": 118.8, + "y": 246.0, + "width": 38.4, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "none", + "originalText": "none", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3780bde8bfbaaa72c74b", + "type": "text", + "x": 242.8, + "y": 246.0, + "width": 460.79999999999995, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "nothing at all, this instruction is from nowhere", + "originalText": "nothing at all, this instruction is from nowhere", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2ee2e61af18522458d0d", + "type": "text", + "x": 731.5999999999999, + "y": 246.0, + "width": 9.6, + "height": 20.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "1", + "originalText": "1", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d3a83ad2a0aebd94c6f6", + "type": "line", + "x": 0.0, + "y": 276.0, + "width": 822.4, + "height": 0.0, + "angle": 0, + "strokeColor": "#ced4da", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0.0 + ], + [ + 822.4, + 0.0 + ] + ], + "lastCommittedPoint": null + }, + { + "id": "a198d1b7e1af7d5b8a7a", + "type": "text", + "x": 0.0, + "y": 296.0, + "width": 952.56, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Most instructions are on one line in the first eighty columns, which is what the two byte short form is for.", + "originalText": "Most instructions are on one line in the first eighty columns, which is what the two byte short form is for.", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.svg b/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.svg new file mode 100644 index 0000000..4ff2f5d --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.svg @@ -0,0 +1 @@ +The compiler picks the smallest form that fitscodeformwhat it storesbytes0 to 9shortthe column, in the code and one more byte210 to 12one linea line delta of 0, 1 or 2, and two columns313no columnsa signed line delta, nothing else214longsigned line delta, end line, both columnsup to 2515nonenothing at all, this instruction is from nowhere1Most instructions are on one line in the first eighty columns, which is what the two byte short form is for. diff --git a/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.excalidraw b/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.excalidraw new file mode 100644 index 0000000..fbf3b63 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.excalidraw @@ -0,0 +1,403 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "a564541273083ed2655d", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 937.8000000000001, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "One byte at the front of every location entry, and it is doing three jobs", + "originalText": "One byte at the front of every location entry, and it is doing three jobs", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "2c50d28c5e07efbe4e0c", + "type": "rectangle", + "x": 0.0, + "y": 50.0, + "width": 656.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "4be2d977184a36c0011a", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "4be2d977184a36c0011a", + "type": "text", + "x": 16.0, + "y": 66.0, + "width": 624.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "then zero or more bytes with bit 7 clear", + "originalText": "then zero or more bytes with bit 7 clear", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "2c50d28c5e07efbe4e0c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b8e1061ad5011a9d14c0", + "type": "text", + "x": 672.0, + "y": 66.0, + "width": 25.2, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "top", + "originalText": "top", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3821280d9babee14675a", + "type": "rectangle", + "x": 0.0, + "y": 102.0, + "width": 656.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3e4d3d74997596642558", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3e4d3d74997596642558", + "type": "text", + "x": 16.0, + "y": 118.0, + "width": 624.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "bits 0 to 2, how many code units it covers, less one", + "originalText": "bits 0 to 2, how many code units it covers, less one", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "3821280d9babee14675a", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "3fd28e406f202c1476c4", + "type": "rectangle", + "x": 0.0, + "y": 154.0, + "width": 656.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "2708897267acf225685b", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "2708897267acf225685b", + "type": "text", + "x": 16.0, + "y": 170.0, + "width": 624.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "bits 3 to 6, which of the six forms this is", + "originalText": "bits 3 to 6, which of the six forms this is", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "3fd28e406f202c1476c4", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "b2d14f7298b21a267b98", + "type": "rectangle", + "x": 0.0, + "y": 206.0, + "width": 656.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#6741d9", + "backgroundColor": "#d0bfff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c83453228546f4916934", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c83453228546f4916934", + "type": "text", + "x": 16.0, + "y": 222.0, + "width": 624.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "bit 7 set, so this byte starts an entry", + "originalText": "bit 7 set, so this byte starts an entry", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "b2d14f7298b21a267b98", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d2568d0b2de19220acd1", + "type": "text", + "x": 0.0, + "y": 278.0, + "width": 849.8399999999999, + "height": 20.0, + "angle": 0, + "strokeColor": "#5c5f66", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "The top bit is what lets you land in the middle of the table and walk backwards to a boundary.", + "originalText": "The top bit is what lets you land in the middle of the table and walk backwards to a boundary.", + "fontSize": 16, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.svg b/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.svg new file mode 100644 index 0000000..81ec754 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.svg @@ -0,0 +1 @@ +One byte at the front of every location entry, and it is doing three jobsthen zero or more bytes with bit 7 cleartopbits 0 to 2, how many code units it covers, less onebits 3 to 6, which of the six forms this isbit 7 set, so this byte starts an entryThe top bit is what lets you land in the middle of the table and walk backwards to a boundary. diff --git a/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.excalidraw b/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.excalidraw new file mode 100644 index 0000000..0f7d3e7 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.excalidraw @@ -0,0 +1,753 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://github.com/tamnd/cpython-internals", + "elements": [ + { + "id": "932ddf9e9e519e876420", + "type": "text", + "x": 0.0, + "y": 0.0, + "width": 798.8400000000001, + "height": 30.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "Six bits to a byte in both, and the chunks go opposite ways", + "originalText": "Six bits to a byte in both, and the chunks go opposite ways", + "fontSize": 24, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "6bbe87b2bb26cfd1ed46", + "type": "text", + "x": 123.25, + "y": 50.0, + "width": 205.5, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the exception table", + "originalText": "the exception table", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "d47b5ff7a5dcb2e23890", + "type": "rectangle", + "x": 0.0, + "y": 87.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#1971c2", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "d6e5f12005d39e2d2a90", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "d6e5f12005d39e2d2a90", + "type": "text", + "x": 16.0, + "y": 103.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "most significant chunk first", + "originalText": "most significant chunk first", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d47b5ff7a5dcb2e23890", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d701480976ca5ac2d76f", + "type": "rectangle", + "x": 0.0, + "y": 147.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "af1975af64d8d09c9247", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "af1975af64d8d09c9247", + "type": "text", + "x": 16.0, + "y": 163.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "bit 6 means another byte follows", + "originalText": "bit 6 means another byte follows", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "d701480976ca5ac2d76f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "eff833affd373c2e529f", + "type": "rectangle", + "x": 0.0, + "y": 207.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "608026dceab07f810e53", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "608026dceab07f810e53", + "type": "text", + "x": 16.0, + "y": 223.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "read with parse_varint", + "originalText": "read with parse_varint", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "eff833affd373c2e529f", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "e811e6a80dbdbae1c58e", + "type": "rectangle", + "x": 0.0, + "y": 267.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "c6dbf05c512776b37afd", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "c6dbf05c512776b37afd", + "type": "text", + "x": 16.0, + "y": 283.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "so a prefix is already the big part", + "originalText": "so a prefix is already the big part", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "e811e6a80dbdbae1c58e", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "d65e5798272b64dda4d6", + "type": "text", + "x": 665.25, + "y": 50.0, + "width": 145.5, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "customData": null, + "text": "the line table", + "originalText": "the line table", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "lineHeight": 1.25, + "autoResize": true + }, + { + "id": "3ddc241910bec980f0d3", + "type": "rectangle", + "x": 512.0, + "y": 87.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#099268", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "705e59a75ce6bdfab758", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "705e59a75ce6bdfab758", + "type": "text", + "x": 528.0, + "y": 103.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "least significant chunk first", + "originalText": "least significant chunk first", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "3ddc241910bec980f0d3", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "413ba38088534ef21f3c", + "type": "rectangle", + "x": 512.0, + "y": 147.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3d0b30801a7ddac54ced", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3d0b30801a7ddac54ced", + "type": "text", + "x": 528.0, + "y": 163.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "bit 6 means another byte follows", + "originalText": "bit 6 means another byte follows", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "413ba38088534ef21f3c", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c36a9cbf1da91be1a71d", + "type": "rectangle", + "x": 512.0, + "y": 207.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "0c9d06c62060363ef46f", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "0c9d06c62060363ef46f", + "type": "text", + "x": 528.0, + "y": 223.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "written with write_varint", + "originalText": "written with write_varint", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "c36a9cbf1da91be1a71d", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "0cce40f15dbd7bd131dc", + "type": "rectangle", + "x": 512.0, + "y": 267.0, + "width": 452.0, + "height": 57.0, + "angle": 0, + "strokeColor": "#495057", + "backgroundColor": "#e9ecef", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "3547ed4e09f57b851126", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "3547ed4e09f57b851126", + "type": "text", + "x": 528.0, + "y": 283.0, + "width": 420.0, + "height": 25.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "so it is written as it is computed", + "originalText": "so it is written as it is computed", + "fontSize": 20, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "containerId": "0cce40f15dbd7bd131dc", + "lineHeight": 1.25, + "autoResize": false + }, + { + "id": "c3b414af3d6629246927", + "type": "rectangle", + "x": 0.0, + "y": 344.0, + "width": 964.0, + "height": 82.0, + "angle": 0, + "strokeColor": "#e8590c", + "backgroundColor": "#ffd8a8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [ + { + "id": "b571bfe6eea4b96bb630", + "type": "text" + } + ], + "updated": 1, + "link": null, + "locked": false + }, + { + "id": "b571bfe6eea4b96bb630", + "type": "text", + "x": 16.0, + "y": 360.0, + "width": 932.0, + "height": 50.0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1, + "version": 1, + "versionNonce": 1, + "isDeleted": false, + "boundElements": [], + "updated": 1, + "link": null, + "locked": false, + "text": "Not an accident. The exception table is binary searched on its first number, and\ncomparing the leading chunk first is what makes that cheap.", + "originalText": "Not an accident. The exception table is binary searched on its first number, and\ncomparing the leading chunk first is what makes that cheap.", + "fontSize": 20, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "c3b414af3d6629246927", + "lineHeight": 1.25, + "autoResize": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.svg b/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.svg new file mode 100644 index 0000000..dc09af0 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.svg @@ -0,0 +1 @@ +Six bits to a byte in both, and the chunks go opposite waysthe exception tablemost significant chunk firstbit 6 means another byte followsread with parse_varintso a prefix is already the big partthe line tableleast significant chunk firstbit 6 means another byte followswritten with write_varintso it is written as it is computedNot an accident. The exception table is binary searched on its first number, andcomparing the leading chunk first is what makes that cheap. diff --git a/lessons/f11-two-tables-on-the-side/f11.ipynb b/lessons/f11-two-tables-on-the-side/f11.ipynb new file mode 100644 index 0000000..c8f7938 --- /dev/null +++ b/lessons/f11-two-tables-on-the-side/f11.ipynb @@ -0,0 +1,686 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f11-01", + "metadata": {}, + "source": [ + "# F11. Two tables on the side\n", + "\n", + "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/tamnd/cpython-internals/blob/main/lessons/f11-two-tables-on-the-side/f11.ipynb)\n", + "\n", + "Write a `try` around a loop and the loop does not get any slower. The instructions inside the `try` are the same ones you get without it, in the same order, at the same offsets.\n", + "\n", + "Nothing in the bytecode says a `try` started or ended. What CPython has instead is a table saying which range of offsets each handler covers, and nothing reads it until something raises. That is zero cost exception handling.\n", + "\n", + "Source locations work the same way. Every instruction knows its line and columns, which is how a traceback underlines the failing part of an expression, and those four numbers per instruction live in a second blob beside the bytecode.\n", + "\n", + "Two tables, same idea. This lesson decodes both.\n", + "\n", + "![the loop instructions on one side and the two side tables on the other](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f11-two-tables-on-the-side/diagrams/nothing-in-the-hot-path.svg)" + ] + }, + { + "cell_type": "markdown", + "id": "f11-02", + "metadata": {}, + "source": [ + "## About the source references\n", + "\n", + "Now and then this lesson points at CPython's own source, like this: `Include/cpython/code.h:314-325@v3.15.0rc1`.\n", + "\n", + "Read it as three parts: the file, the lines, and the release those line numbers belong to. Sometimes there is a fourth part after a `#`, which is the name of the thing those lines are inside.\n", + "\n", + "Every reference is a link, and every one is checked against the pinned source on each change, so a stale reference fails the build instead of sending you somewhere wrong. You never have to read any of it. The references are there so you can go deeper when you want to, and so you can check that this lesson is not making things up.\n", + "\n", + "## Setup\n", + "\n", + "Colab does not come with the small package these lessons use, so the next cell installs it. If you are running this from a checkout of the repository it is already installed and the cell does nothing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-03", + "metadata": {}, + "outputs": [], + "source": [ + "import sys\n", + "\n", + "if sys.version_info < (3, 14):\n", + " print(\"This lesson needs CPython 3.14 or newer.\")\n", + " print(f\"This runtime is {sys.version.split()[0]}, and the cells below will not run on it.\")\n", + "else:\n", + " try:\n", + " import pyxray\n", + " except ImportError:\n", + " %pip install -q \"pyxray @ git+https://github.com/tamnd/cpython-internals@main#subdirectory=pyxray\"\n", + " import pyxray" + ] + }, + { + "cell_type": "markdown", + "id": "f11-04", + "metadata": {}, + "source": [ + "## Which Python is this\n", + "\n", + "Everything below was checked against the version this cell prints and against 3.14. Where the two disagree, the lesson says so." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-05", + "metadata": { + "cpython_internals": { + "differs": "This prints the interpreter you are on, so it is different for everybody." + } + }, + "outputs": [], + "source": [ + "import pyxray\n", + "\n", + "pyxray.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f11-06", + "metadata": {}, + "source": [ + "## The try that is not there\n", + "\n", + "Start with the claim, because it is easy to check and hard to believe.\n", + "\n", + "Take a loop. Wrap it in a `try` with an `except` after it. Then compare the instructions inside the covered range against the instructions of the same loop with no `try` around it at all.\n", + "\n", + "the instructions inside a try block are identical to the same code with no try around it" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-07", + "metadata": { + "cpython_internals": { + "varies": "The offsets are 3.15's. On 3.14 the loop compiles two bytes shorter so every number here shifts down, and the two lists still match, which is the part that matters." + } + }, + "outputs": [], + "source": [ + "import dis\n", + "\n", + "PLAIN = \"def f(xs):\\n t = 0\\n for x in xs:\\n t += x\\n return t\\n\"\n", + "TRIED = (\n", + " \"def f(xs):\\n t = 0\\n try:\\n for x in xs:\\n t += x\\n\"\n", + " \" except TypeError:\\n t = -1\\n return t\\n\"\n", + ")\n", + "\n", + "plain = compile(PLAIN, \"\", \"exec\").co_consts[0]\n", + "tried = compile(TRIED, \"\", \"exec\").co_consts[0]\n", + "\n", + "covered = dis._parse_exception_table(tried)[0]\n", + "inside = [\n", + " one.opname for one in dis.get_instructions(tried) if covered.start <= one.offset < covered.end\n", + "]\n", + "loop = [one.opname for one in dis.get_instructions(plain)][3:-2]\n", + "\n", + "print(f\" the try covers bytes {covered.start} to {covered.end}, handler at {covered.target}\")\n", + "print()\n", + "print(f\" inside the try {len(inside)} instructions\")\n", + "print(f\" the plain loop {len(loop)} instructions\")\n", + "print(f\" the same list {inside == loop}\")\n", + "print()\n", + "total = len(list(dis.get_instructions(tried)))\n", + "print(f\" the try version has {total} instructions in total,\")\n", + "print(f\" and the {total - len(inside)} outside it are the setup, the return and the handler\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-08", + "metadata": {}, + "source": [ + "> **Version note.** The offsets are 3.15's. On 3.14 the loop compiles two bytes shorter so every number here shifts down, and the two lists still match, which is the part that matters." + ] + }, + { + "cell_type": "markdown", + "id": "f11-09", + "metadata": {}, + "source": [ + "Two pseudo instructions used to be there. `SETUP_FINALLY` said \"from here on, exceptions go to L1\", and `POP_BLOCK` undid it. They still exist in the compiler, because the code generator finds them convenient to think in, and F08 watched the optimizer move them around. The assembler deletes both and writes the exception table instead, which is `Python/assemble.c:158-190@v3.15.0rc1#assemble_exception_table`.\n", + "\n", + "So neither one survives into the bytecode. Worth checking rather than believing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-10", + "metadata": { + "cpython_internals": { + "varies": "The table's bytes are 3.15's. On 3.14 the same function needs twelve bytes rather than sixteen, because it has one fewer handler entry." + } + }, + "outputs": [], + "source": [ + "names = {one.opname for one in dis.get_instructions(tried)}\n", + "\n", + "for one in (\"SETUP_FINALLY\", \"POP_BLOCK\", \"SETUP_CLEANUP\", \"SETUP_WITH\"):\n", + " print(f\" {one:16} in the bytecode: {one in names}\")\n", + "\n", + "print()\n", + "print(f\" and the whole exception table is {len(tried.co_exceptiontable)} bytes:\")\n", + "print(f\" {' '.join(f'{b:02x}' for b in tried.co_exceptiontable)}\")\n", + "print(f\" the plain version's table is {len(plain.co_exceptiontable)} bytes\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-11", + "metadata": {}, + "source": [ + "> **Version note.** The table's bytes are 3.15's. On 3.14 the same function needs twelve bytes rather than sixteen, because it has one fewer handler entry." + ] + }, + { + "cell_type": "markdown", + "id": "f11-12", + "metadata": {}, + "source": [ + "## Four numbers, and one of them is doubled\n", + "\n", + "An exception table entry is conceptually five things: where the covered range starts, where it ends, where the handler is, how deep the stack should be when the handler starts, and whether the offset of the failing instruction has to be pushed too.\n", + "\n", + "The last one is `lasti`, and it is there for re raising. At the end of a `finally` block an in flight exception has to be raised again and has to keep pointing at the instruction that first raised it, but by then the instruction pointer is somewhere inside the `finally`. So the original offset gets pushed on the stack and `RERAISE` puts it back.\n", + "\n", + "Five things get stored as four, because the size is always smaller than the end, so `start, size, target, depth` is cheaper than `start, end, target, depth`, and `depth` and `lasti` share a number as `depth * 2 + lasti`. That is `Python/assemble.c:133-156@v3.15.0rc1#assemble_emit_exception_table_entry`.\n", + "\n", + "![the four bytes of one exception table entry, labelled](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f11-two-tables-on-the-side/diagrams/one-entry-decoded.svg)\n", + "\n", + "Every number in the encoding counts code units rather than bytes, so everything doubles on the way out.\n", + "\n", + "an exception table entry decodes to start, size, target and a doubled depth" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-13", + "metadata": { + "cpython_internals": { + "varies": "These four bytes are 3.15's. On 3.14 they read 84 11 17 00, which is start 4, size 17, target 23, and the walk through them is the same walk." + } + }, + "outputs": [], + "source": [ + "table = tried.co_exceptiontable\n", + "\n", + "print(f\" {' '.join(f'{b:02x}' for b in table)}\")\n", + "print()\n", + "for i, b in enumerate(table[:4]):\n", + " starts = \"yes\" if b & 0x80 else \"no\"\n", + " more = \"yes\" if b & 0x40 else \"no\"\n", + " print(f\" byte {i}: {b:3} {b:08b} starts: {starts:3} more: {more:3} value {b & 0x3F}\")\n", + "\n", + "start, size, target, both = (b & 0x3F for b in table[:4])\n", + "\n", + "print()\n", + "print(f\" so start {start}, size {size}, target {target}, and depth and lasti packed into {both}\")\n", + "print(f\" which covers bytes {start * 2} to {(start + size) * 2}, handler at byte {target * 2}\")\n", + "print(f\" with the stack popped back to {both >> 1} and lasti {bool(both & 1)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-14", + "metadata": {}, + "source": [ + "> **Version note.** These four bytes are 3.15's. On 3.14 they read 84 11 17 00, which is start 4, size 17, target 23, and the walk through them is the same walk." + ] + }, + { + "cell_type": "markdown", + "id": "f11-15", + "metadata": {}, + "source": [ + "That is the whole format. The top bit marks the first byte of an entry, bit 6 says another byte follows, and the low six bits carry the value. Which means the entire table can be decoded in about a dozen lines.\n", + "\n", + "Here is a decoder, and next to it what `dis` says, so there is nothing to take on trust.\n", + "\n", + "a dozen lines of Python decode co_exceptiontable exactly as dis does" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-16", + "metadata": { + "cpython_internals": { + "varies": "The offsets are 3.15's and 3.14 puts them a few bytes lower. The line that matters is the last one, and it says True on both." + } + }, + "outputs": [], + "source": [ + "import dis\n", + "\n", + "\n", + "def handlers(table):\n", + " \"\"\"Every entry in a co_exceptiontable, as byte offsets rather than code units.\"\"\"\n", + "\n", + " def number(at):\n", + " byte = table[at]\n", + " value = byte & 0x3F\n", + " while byte & 0x40:\n", + " at += 1\n", + " byte = table[at]\n", + " value = (value << 6) | (byte & 0x3F)\n", + " return value, at + 1\n", + "\n", + " at = 0\n", + " while at < len(table):\n", + " start, at = number(at)\n", + " size, at = number(at)\n", + " target, at = number(at)\n", + " both, at = number(at)\n", + " yield start * 2, (start + size) * 2, target * 2, both >> 1, bool(both & 1)\n", + "\n", + "\n", + "def guarded(items):\n", + " total = 0\n", + " try:\n", + " for one in items:\n", + " total = total + one\n", + " except TypeError:\n", + " total = -1\n", + " finally:\n", + " print(total)\n", + " return total\n", + "\n", + "\n", + "mine = list(handlers(guarded.__code__.co_exceptiontable))\n", + "theirs = [tuple(one) for one in dis._parse_exception_table(guarded.__code__)]\n", + "\n", + "print(\" start end target depth lasti\")\n", + "for one in mine:\n", + " print(f\" {one[0]:5} {one[1]:3} {one[2]:6} {one[3]:5} {one[4]}\")\n", + "print()\n", + "print(f\" {len(mine)} entries in {len(guarded.__code__.co_exceptiontable)} bytes\")\n", + "print(f\" and dis agrees on every one of them: {mine == theirs}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-17", + "metadata": {}, + "source": [ + "> **Version note.** The offsets are 3.15's and 3.14 puts them a few bytes lower. The line that matters is the last one, and it says True on both." + ] + }, + { + "cell_type": "markdown", + "id": "f11-18", + "metadata": {}, + "source": [ + "## Why the top bit is set\n", + "\n", + "The marker bit looks like a small thing and it is the reason the format was chosen.\n", + "\n", + "When something raises, the interpreter has an offset and needs the entry covering it. Entries vary in size, so you cannot index into the table. But because every entry starts with a byte that has the top bit set, and no other byte in an entry does, you can land anywhere in the middle of the table and walk backwards until you find one. That is `Python/ceval.h:440-444@v3.15.0rc1#scan_back_to_entry_start`, four lines long.\n", + "\n", + "Which means binary search works on a variable length table. Jump to the middle, walk back to the nearest entry boundary, read its start offset, and go left or right. `Python/ceval.h:457-490@v3.15.0rc1#get_exception_handler` does exactly that, and falls back to a straight scan once the range is under forty bytes, which is where a binary search stops being worth the trouble.\n", + "\n", + "![raise, look up the offset, unwind the stack, jump to the handler](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f11-two-tables-on-the-side/diagrams/how-a-raise-finds-its-handler.svg)\n", + "\n", + "The depth in the entry is what the second step needs. When a handler starts, the stack has to look the way it looked when the `try` began, whatever the failing code left on it. Rather than tracking that at run time, the compiler works it out once and writes it down." + ] + }, + { + "cell_type": "markdown", + "id": "f11-19", + "metadata": {}, + "source": [ + "## Every instruction knows where it came from\n", + "\n", + "Now the other table. This one is bigger and the format is more interesting.\n", + "\n", + "`co_positions()` gives four numbers per instruction: start line, end line, start column, end column. That is what lets a traceback underline the failing part of an expression instead of the whole line. Here is that underline, rebuilt from the table by hand rather than printed by `traceback`, so you can see exactly where it comes from.\n", + "\n", + "the caret in a traceback is drawn from the column numbers in co_linetable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-20", + "metadata": { + "cpython_internals": { + "varies": "3.14 reaches the failure one instruction earlier, so the instruction number differs. The line and the columns it reports, and so the caret, are the same on both." + } + }, + "outputs": [], + "source": [ + "SOURCE = \"\"\"def totals(first, second):\n", + " return first[\"count\"] + second[\"count\"]\n", + "\n", + "totals({\"count\": 1}, {\"total\": 2})\n", + "\"\"\"\n", + "\n", + "lines = SOURCE.splitlines()\n", + "\n", + "try:\n", + " exec(compile(SOURCE, \"example.py\", \"exec\"), {})\n", + "except KeyError as problem:\n", + " deepest = problem.__traceback__\n", + " while deepest.tb_next:\n", + " deepest = deepest.tb_next\n", + " inner = deepest.tb_frame.f_code\n", + " line, end_line, start, end = list(inner.co_positions())[deepest.tb_lasti // 2]\n", + " print(f\" it failed at instruction {deepest.tb_lasti // 2}, which the table puts at\")\n", + " print(f\" line {line}, columns {start} to {end}\")\n", + " print()\n", + " print(\" \", lines[line - 1])\n", + " print(\" \", \" \" * start + \"^\" * (end - start))" + ] + }, + { + "cell_type": "markdown", + "id": "f11-21", + "metadata": {}, + "source": [ + "> **Version note.** 3.14 reaches the failure one instruction earlier, so the instruction number differs. The line and the columns it reports, and so the caret, are the same on both." + ] + }, + { + "cell_type": "markdown", + "id": "f11-22", + "metadata": {}, + "source": [ + "## Six ways to say where\n", + "\n", + "Storing four numbers for every instruction plainly would cost sixteen bytes each, which for a small function is more than the bytecode. So the format has six shapes and the assembler picks the smallest one that fits.\n", + "\n", + "Every entry begins with one byte carrying three things: a marker bit, which of the six forms this is, and how many code units the entry covers. `Include/internal/pycore_code.h:432-437@v3.15.0rc1#write_location_entry_start` is the one line that builds it.\n", + "\n", + "![the three fields packed into the first byte of a location entry](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f11-two-tables-on-the-side/diagrams/the-first-byte-says-what-follows.svg)\n", + "\n", + "The six forms are `Include/cpython/code.h:314-325@v3.15.0rc1`, and the reason there are six is that almost every instruction is boring. Same line as the one before, columns under eighty, span under sixteen characters. That case is two bytes.\n", + "\n", + "![the six location entry forms and what each one stores](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f11-two-tables-on-the-side/diagrams/six-ways-to-say-where.svg)\n", + "\n", + "Form 15 is the odd one. Some instructions did not come from your source at all, and they get no location. In a function with a `try` in it they are the exception plumbing, the instructions that save the in flight exception and re raise it, which no line you wrote asked for.\n", + "\n", + "some instructions in a normal function have no source location at all" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-23", + "metadata": { + "cpython_internals": { + "varies": "The counts and the byte sizes are 3.15's. 3.14 compiles the same function a little smaller, and the instructions with no location are the same exception plumbing either way." + } + }, + "outputs": [], + "source": [ + "import dis\n", + "\n", + "where = list(guarded.__code__.co_positions())\n", + "nowhere = [one.opname for one in dis.get_instructions(guarded) if where[one.offset // 2][0] is None]\n", + "\n", + "print(f\" {len(list(dis.get_instructions(guarded)))} instructions in guarded\")\n", + "print(f\" {len(nowhere)} of them come from nowhere, and they are {sorted(set(nowhere))}\")\n", + "print()\n", + "print(f\" co_code {len(guarded.__code__.co_code):4} bytes\")\n", + "print(f\" co_linetable {len(guarded.__code__.co_linetable):4} bytes\")\n", + "print(f\" co_exceptiontable {len(guarded.__code__.co_exceptiontable):4} bytes\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-24", + "metadata": {}, + "source": [ + "> **Version note.** The counts and the byte sizes are 3.15's. 3.14 compiles the same function a little smaller, and the instructions with no location are the same exception plumbing either way." + ] + }, + { + "cell_type": "markdown", + "id": "f11-25", + "metadata": {}, + "source": [ + "## Decoding it\n", + "\n", + "Thirty lines. The only fiddly parts are the two variable length integers and one off by one.\n", + "\n", + "The off by one is that the long form stores each column plus one, so that zero can mean \"no column here\". `Python/assemble.c:257-267@v3.15.0rc1#write_location_info_long_form` adds the one on the way in and `Objects/codeobject.c:1201-1225@v3.15.0rc1#advance_with_locations` takes it off on the way out. The internal documentation does not mention it, which is a reasonable reminder that the source is the specification.\n", + "\n", + "a hand written decoder reproduces co_positions() for every code object in a standard library module" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-26", + "metadata": {}, + "outputs": [], + "source": [ + "def positions(code):\n", + " \"\"\"Every instruction's source location, decoded out of co_linetable by hand.\"\"\"\n", + " table = code.co_linetable\n", + " line = code.co_firstlineno\n", + " at = 0\n", + "\n", + " def varint():\n", + " nonlocal at\n", + " value = shift = 0\n", + " while True:\n", + " chunk = table[at]\n", + " at += 1\n", + " value |= (chunk & 63) << shift\n", + " shift += 6\n", + " if not chunk & 64:\n", + " return value\n", + "\n", + " def svarint():\n", + " value = varint()\n", + " return -(value >> 1) if value & 1 else value >> 1\n", + "\n", + " while at < len(table):\n", + " first = table[at]\n", + " at += 1\n", + " kind = (first >> 3) & 15\n", + " length = (first & 7) + 1\n", + " if kind == 15:\n", + " found = (None, None, None, None)\n", + " elif kind == 13:\n", + " line += svarint()\n", + " found = (line, line, None, None)\n", + " elif kind == 14:\n", + " line += svarint()\n", + " end_line = line + varint()\n", + " start, end = varint() - 1, varint() - 1\n", + " found = (line, end_line, start if start >= 0 else None, end if end >= 0 else None)\n", + " elif kind >= 10:\n", + " line += kind - 10\n", + " found = (line, line, table[at], table[at + 1])\n", + " at += 2\n", + " else:\n", + " second = table[at]\n", + " at += 1\n", + " start = kind * 8 + ((second >> 4) & 7)\n", + " found = (line, line, start, start + (second & 15))\n", + " yield from [found] * length" + ] + }, + { + "cell_type": "markdown", + "id": "f11-27", + "metadata": {}, + "source": [ + "Now run it against something. Not one hand picked function, because that proves nothing, but every code object in a handful of standard library modules, nested functions and comprehensions and all." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-28", + "metadata": { + "cpython_internals": { + "varies": "How many code objects those four modules hold depends on the version, because the modules themselves change. The number that has to be zero is zero on both." + } + }, + "outputs": [], + "source": [ + "import argparse\n", + "import dataclasses\n", + "import json.decoder\n", + "import types\n", + "\n", + "\n", + "def everything(code):\n", + " \"\"\"This code object and every one nested inside it.\"\"\"\n", + " yield code\n", + " for one in code.co_consts:\n", + " if isinstance(one, types.CodeType):\n", + " yield from everything(one)\n", + "\n", + "\n", + "def written_in(module):\n", + " \"\"\"Every code object belonging to functions and methods defined in this module.\"\"\"\n", + " for one in vars(module).values():\n", + " if isinstance(one, types.FunctionType) and one.__module__ == module.__name__:\n", + " yield from everything(one.__code__)\n", + " elif isinstance(one, type) and one.__module__ == module.__name__:\n", + " for other in vars(one).values():\n", + " if isinstance(other, types.FunctionType):\n", + " yield from everything(other.__code__)\n", + "\n", + "\n", + "checked = wrong = 0\n", + "for module in (argparse, dataclasses, dis, json.decoder):\n", + " for one in written_in(module):\n", + " checked += 1\n", + " if list(positions(one)) != list(one.co_positions()):\n", + " wrong += 1\n", + " print(f\" mismatch in {module.__name__}.{one.co_name}\")\n", + "\n", + "print(f\" {checked} code objects out of four standard library modules\")\n", + "print(f\" {wrong} of them decoded differently from co_positions()\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-29", + "metadata": {}, + "source": [ + "> **Version note.** How many code objects those four modules hold depends on the version, because the modules themselves change. The number that has to be zero is zero on both." + ] + }, + { + "cell_type": "markdown", + "id": "f11-30", + "metadata": {}, + "source": [ + "## Two varints, opposite ways round\n", + "\n", + "One last detail, and it is the kind of thing that looks like sloppiness until you see why.\n", + "\n", + "Both tables encode numbers six bits to a byte with bit 6 meaning \"another byte follows\". They put the chunks in opposite orders. The line table writes the least significant chunk first, in `Include/internal/pycore_code.h:405-416@v3.15.0rc1#write_varint`. The exception table reads the most significant chunk first, in `Include/internal/pycore_code.h:394-403@v3.15.0rc1#parse_varint`.\n", + "\n", + "![the two variable length integer encodings side by side](https://raw.githubusercontent.com/tamnd/cpython-internals/main/lessons/f11-two-tables-on-the-side/diagrams/two-varints-in-one-file.svg)\n", + "\n", + "The reason is the binary search. The exception table gets searched on the start offset of each entry, and reading the leading chunk of a most significant first number gives you the big end straight away. The line table is never searched that way, it is walked from the beginning, so it uses whichever order is easier to write.\n", + "\n", + "the two tables encode integers with their chunks in opposite orders" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f11-31", + "metadata": {}, + "outputs": [], + "source": [ + "def as_exception_table(value):\n", + " \"\"\"How the exception table would write this number: biggest chunk first.\"\"\"\n", + " chunks = []\n", + " while value >= 64:\n", + " chunks.append(value & 63)\n", + " value >>= 6\n", + " chunks.append(value)\n", + " chunks.reverse()\n", + " out = [one | 0x40 for one in chunks[:-1]] + [chunks[-1]]\n", + " out[0] |= 0x80\n", + " return out\n", + "\n", + "\n", + "def as_line_table(value):\n", + " \"\"\"How the line table would write it: smallest chunk first.\"\"\"\n", + " out = []\n", + " while value >= 64:\n", + " out.append(64 | (value & 63))\n", + " value >>= 6\n", + " out.append(value)\n", + " return out\n", + "\n", + "\n", + "for number in (5, 100, 4000, 100000):\n", + " one = \" \".join(f\"{b:02x}\" for b in as_exception_table(number))\n", + " other = \" \".join(f\"{b:02x}\" for b in as_line_table(number))\n", + " print(f\" {number:6} exception table {one:14} line table {other}\")\n", + "\n", + "print()\n", + "print(\" same number, same six bit chunks, read from opposite ends\")" + ] + }, + { + "cell_type": "markdown", + "id": "f11-32", + "metadata": {}, + "source": [ + "## Try it yourself\n", + "\n", + "Three things to poke at.\n", + "\n", + "Write a function with a `try` inside a `try` inside a loop, and print its exception table with the decoder above. The nesting shows up as overlapping ranges, and the depth column is what tells the handlers apart.\n", + "\n", + "Take a function with a long expression spread over several lines and print `co_positions()` next to `co_lines()`. The second is smaller and older and only gives you line numbers. Work out from the entry forms why.\n", + "\n", + "Compile the same function twice, once with the body all on one line and once spread over ten, and compare the length of `co_linetable`. The bytecode is identical. The table is not, and the difference is entirely which entry forms the assembler could get away with.\n", + "\n", + "## What just happened\n", + "\n", + "A `try` costs nothing when nothing raises, because nothing about it is in the bytecode. The pseudo instructions the compiler used are deleted by the assembler, and what replaces them is a table of ranges.\n", + "\n", + "That table holds start, size, target and a number that is the stack depth doubled plus a re raise flag, all in code units, all as six bit chunks. The top bit of the first byte of each entry is what makes a variable length table binary searchable, because you can land anywhere and walk backwards to a boundary.\n", + "\n", + "The line table is the same idea for source locations. Four numbers per instruction, six entry forms, and the assembler picks the smallest that fits. It is what draws the caret under the failing half of an expression in a traceback.\n", + "\n", + "Both are decodable in about thirty lines of Python, and both decoders agree with the interpreter, which is the only evidence worth having.\n", + "\n", + "## What is next\n", + "\n", + "F12 is marshal, which is how all of this gets written to a `.pyc` file and read back. It is the last lesson of the front end, and it closes the loop: source text in at F01, bytes on disk at F12, and the same code object at both ends." + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/probes/pyodide/lessons.json b/probes/pyodide/lessons.json index cf9c335..2dac2c1 100644 --- a/probes/pyodide/lessons.json +++ b/probes/pyodide/lessons.json @@ -539,7 +539,7 @@ { "name": "f10-11", "status": "ok", - "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" + "printed": " -- MAKE_CELL 0 (a)\n MAKE_CELL 6 (n)\n\n 1 RESUME 0\n\n 2 LOAD_SMALL_INT 0\n STORE_DEREF 6 (n)\n\n 4 LOAD_FAST_BORROW 0 (a)\n LOAD_FAST_BORROW 6 (n)\n BUILD_TUPLE 2\n LOAD_CONST 1 ()\n MAKE_FUNCTION\n SET_FUNCTION_ATTRIBUTE 8 (closure)\n STORE_FAST 5 (inner)\n\n 7 LOAD_FAST_BORROW 5 (inner)\n RETURN_VALUE\n\nDisassembly of :\n -- COPY_FREE_VARS 2\n\n 4 RESUME 0\n\n 5 LOAD_DEREF 0 (a)\n LOAD_DEREF 1 (n)\n BINARY_OP 0 (+)\n RETURN_VALUE\n" }, { "name": "f10-13", @@ -558,6 +558,64 @@ } ] }, + { + "slug": "f11-two-tables-on-the-side", + "cells": [ + { + "name": "f11-03", + "status": "ok" + }, + { + "name": "f11-05", + "status": "ok", + "printed": "cpython 3.14.2 on Emscripten wasm32, WebAssembly\n note: this is 3.14, and everything here is written against 3.15, so bytecode and some object layouts will differ from the prose\n" + }, + { + "name": "f11-07", + "status": "ok", + "printed": " the try covers bytes 8 to 42, handler at 46\n\n inside the try 10 instructions\n the plain loop 10 instructions\n the same list True\n\n the try version has 31 instructions in total,\n and the 21 outside it are the setup, the return and the handler\n" + }, + { + "name": "f11-10", + "status": "ok", + "printed": " SETUP_FINALLY in the bytecode: False\n POP_BLOCK in the bytecode: False\n SETUP_CLEANUP in the bytecode: False\n SETUP_WITH in the bytecode: False\n\n and the whole exception table is 12 bytes:\n 84 11 17 00 97 0d 28 03 a7 01 28 03\n the plain version's table is 0 bytes\n" + }, + { + "name": "f11-13", + "status": "ok", + "printed": " 84 11 17 00 97 0d 28 03 a7 01 28 03\n\n byte 0: 132 10000100 starts: yes more: no value 4\n byte 1: 17 00010001 starts: no more: no value 17\n byte 2: 23 00010111 starts: no more: no value 23\n byte 3: 0 00000000 starts: no more: no value 0\n\n so start 4, size 17, target 23, and depth and lasti packed into 0\n which covers bytes 8 to 42, handler at byte 46\n with the stack popped back to 0 and lasti False\n" + }, + { + "name": "f11-16", + "status": "ok", + "printed": " start end target depth lasti\n 8 42 68 0 False\n 68 94 100 1 True\n 94 98 106 0 False\n 98 100 100 1 True\n 100 106 106 0 False\n 106 132 132 1 True\n\n 6 entries in 25 bytes\n and dis agrees on every one of them: True\n" + }, + { + "name": "f11-20", + "status": "ok", + "printed": " it failed at instruction 11, which the table puts at\n line 2, columns 28 to 43\n\n return first[\"count\"] + second[\"count\"]\n ^^^^^^^^^^^^^^^\n" + }, + { + "name": "f11-23", + "status": "ok", + "printed": " 43 instructions in guarded\n 8 of them come from nowhere, and they are ['COPY', 'POP_EXCEPT', 'PUSH_EXC_INFO', 'RERAISE']\n\n co_code 138 bytes\n co_linetable 75 bytes\n co_exceptiontable 25 bytes\n" + }, + { + "name": "f11-26", + "status": "ok" + }, + { + "name": "f11-28", + "status": "ok", + "printed": " 294 code objects out of four standard library modules\n 0 of them decoded differently from co_positions()\n" + }, + { + "name": "f11-31", + "status": "ok", + "printed": " 5 exception table 85 line table 05\n 100 exception table c1 24 line table 64 01\n 4000 exception table fe 20 line table 60 3e\n 100000 exception table d8 5a 20 line table 60 5a 18\n\n same number, same six bit chunks, read from opposite ends\n" + } + ] + }, { "slug": "t01-one-line-seven-stages", "cells": [ @@ -940,7 +998,7 @@ { "name": "t04-30", "status": "ok", - "printed": "free variables: ('total',)\nthe cells: (,)\nfirst call: 1\nsecond call: 2\nthe cell now: 2\n" + "printed": "free variables: ('total',)\nthe cells: (,)\nfirst call: 1\nsecond call: 2\nthe cell now: 2\n" }, { "name": "t04-33", @@ -1217,7 +1275,7 @@ { "name": "t07-38", "status": "ok", - "printed": "asked twice, got the same object: True\nand it is still here after the call returned: \nits name: make_one\n" + "printed": "asked twice, got the same object: True\nand it is still here after the call returned: \nits name: make_one\n" }, { "name": "t07-40", @@ -1251,12 +1309,12 @@ { "name": "t08-11", "status": "ok", - "printed": "NoneType at 0x30d7d8, refcount is parked, this object is never freed, 8 bytes, not tracked\nint at 0x31a7cc, refcount is parked, this object is never freed, 16 bytes, not tracked\nstr at 0x15374b8, 3 reference(s), 26 bytes, not tracked\nlist at 0x1685d38, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x1825e68, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x14d0058, 4 reference(s), 84 bytes, tracked by the cycle collector\n" + "printed": "NoneType at 0x30d7d8, refcount is parked, this object is never freed, 8 bytes, not tracked\nint at 0x31a7cc, refcount is parked, this object is never freed, 16 bytes, not tracked\nstr at 0x1638f40, 3 reference(s), 26 bytes, not tracked\nlist at 0x135f4f0, 2 reference(s), 44 bytes, tracked by the cycle collector\ndict at 0x17a4f70, 2 reference(s), 108 bytes, tracked by the cycle collector\nfunction at 0x18b2970, 4 reference(s), 84 bytes, tracked by the cycle collector\n" }, { "name": "t08-14", "status": "ok", - "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0xfe7c48\nid(b) 0x12976b0\nid(c) 0xfe7c48\n" + "printed": "a == b True same contents\na is b False different objects\na is c True same object\n\nid(a) 0x1285510\nid(b) 0x17993c0\nid(c) 0x1285510\n" }, { "name": "t08-17", @@ -1296,7 +1354,7 @@ { "name": "t08-35", "status": "ok", - "printed": "dict at 0x1342e70\ndict at 0x16f4028\nlist at 0x12975c8\n" + "printed": "dict at 0x11f2548\ndict at 0x1342e70\nlist at 0x17992a8\n" }, { "name": "t08-37", @@ -1355,7 +1413,7 @@ { "name": "t09-21", "status": "ok", - "printed": "Node at 0x135a0e0 -> Node at 0x135d318 -> Node at 0x1521170 -> Node at 0x135a0e0\n" + "printed": "Node at 0x113ad28 -> Node at 0x182c188 -> Node at 0x1597008 -> Node at 0x113ad28\n" }, { "name": "t09-23", @@ -1385,7 +1443,7 @@ { "name": "t09-37", "status": "ok", - "printed": "first object was at 0x14b3670\nsecond object is at 0x14b3670\nsame address reused -> True\n" + "printed": "first object was at 0x174a8e8\nsecond object is at 0x174a8e8\nsame address reused -> True\n" } ] }, diff --git a/probes/pyodide/lessons.md b/probes/pyodide/lessons.md index e5d160e..47683cc 100644 --- a/probes/pyodide/lessons.md +++ b/probes/pyodide/lessons.md @@ -2,7 +2,7 @@ Generated by `just build-probe`. Do not edit by hand, the change will be overwritten. -26 lesson(s) on Pyodide 3.14.2: 26 ran end to end, 280 cell(s) in total. +27 lesson(s) on Pyodide 3.14.2: 27 ran end to end, 291 cell(s) in total. The checks in `report.md` next to this ask whether a surface exists. This runs the lessons themselves: every code cell of every notebook, in order, in one Pyodide runtime, with `pyxray` mounted off the disk rather than installed. The install cell is the one thing changed, and only its `%pip` line, which a reader in a browser does not need either. @@ -22,6 +22,7 @@ The checks in `report.md` next to this ask whether a surface exists. This runs t | f08-the-optimizer | 7 | runs end to end | | f09-two-bytes-at-a-time | 7 | runs end to end | | f10-inside-a-code-object | 8 | runs end to end | +| f11-two-tables-on-the-side | 11 | runs end to end | | t01-one-line-seven-stages | 18 | runs end to end | | t02-text-becomes-tokens | 32 | runs end to end | | t03-tokens-become-a-tree | 13 | runs end to end |