Add R06, the C API tiers - #185
Merged
Merged
Conversation
The sixth runtime lesson, and the first one that looks at CPython from the C side rather than from inside Python. The C API is three directories and two macros. `Include/` is open to any extension, `Include/cpython/` needs `Py_LIMITED_API` to be undefined, and `Include/internal/` starts nearly every file with three lines that stop the compiler unless you define `Py_BUILD_CORE`. More than half the header lines are in that third directory. The lesson walks the `Py_LIMITED_API` guard as a preprocessor stack to count what a limited build may call, prints the `#error` that guards 140 of the 148 internal headers, cross tabulates the three naming conventions against the three directories, and then shows that none of it survives the build: every tier resolves through `ctypes.pythonapi`, and calling `_PyDict_SizeOf` by hand gives the same number `dict.__sizeof__` does. Two Tier 1 recordings put the split on record. Inside the internal headers, 93 percent of the names spelled `PyAPI_FUNC` resolve against 0.4 percent of the ones spelled plain `extern`, with 168 comments naming which bundled shared extension needs each export. The free threaded run finds six more, which are the ones behind `Py_GIL_DISABLED`. Half the lesson wants to read the headers and a browser tab does not have them, so the header cells sit behind one flag set in the first of them and say so rather than printing nothing. The other half only needs ctypes, which works everywhere, so eleven of eleven cells run under Pyodide. Six diagrams, four glossary terms, rows in both READMEs.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
R06 is the sixth runtime lesson and the first one that looks at CPython from the C side rather than from inside Python. It answers a question that comes up the moment anybody opens
Python.h: which of these functions am I actually allowed to call, and what happens if I call one I am not.The answer is three directories and two macros.
Include/is open to any extension.Include/cpython/needsPy_LIMITED_APIto be undefined, and the mechanism is nothing more than an#ifndefaround an#includeat the bottom of each public header.Include/internal/starts nearly every file with three lines that stop the compiler with an error unless you definePy_BUILD_CORE. On 3.15 that third directory is 148 files and 42552 lines, which is more than the other two put together.The lesson walks the
Py_LIMITED_APIguard as a preprocessor stack, so the count of what a limited build may call is measured rather than asserted: 580 functions visible in the public headers, 186 taken away by the guard, and 974 in the other two directories it never opens. It then makes the point that the functions are not the expensive part. The struct layouts go too, and with no fields to readPy_TYPEbecomes a function call andPy_DECREFbecomes a call to_Py_DecRef.The naming convention gets cross tabulated against the directories, because people assume the two are the same thing and they nearly are. The seventeen underscore names sitting in the public tier all have one explanation: a private name has to be exported when a public macro expands to it, which is why
_Py_Deallocis a public symbol.Then the part that changes how you read all of it. None of the tiers survive into the binary. Eight names from all three tiers go to
ctypes.pythonapiand every one of them resolves, and_PyDict_SizeOfgets called by hand and returns exactly whatdict.__sizeof__returns, 16 bytes short ofsys.getsizeofbecause that adds the collector header.Two Tier 1 recordings settle whether that is an accident. It is not. Inside the internal headers, 530 names are spelled
PyAPI_FUNCand 493 of them resolve on the release build, against 757 spelled plainexternof which 3 do. Both spellings sit in the same files, often two lines apart, and 168 comments name which bundled shared extension needs each export. The free threaded run finds six more, which are the ones behindPy_GIL_DISABLED, so the missing names were a build flag rather than a mystery.The last cell hands off to R07 by looking at what a built extension does carry, which is its file name. Three suffixes on 3.14 and six on 3.15, and the new ones end in
abi3t.Half of this wants to read the headers, and a browser tab has none. Those cells sit behind one flag set in the first of them and say so out loud rather than printing nothing, the same shape R05 used. The other half only needs ctypes, which works everywhere, so all eleven probed cells run under Pyodide.
Six diagrams, four glossary terms, five
differs=notes checked against a real 3.14 run, and rows in both READMEs.