Skip to content

gh-114314: ctypes: remove stgdict and switch to heap types#116458

Merged
encukou merged 60 commits intopython:mainfrom
encukou:ctypes-no-stgdict
Mar 20, 2024
Merged

gh-114314: ctypes: remove stgdict and switch to heap types#116458
encukou merged 60 commits intopython:mainfrom
encukou:ctypes-no-stgdict

Conversation

@encukou
Copy link
Member

@encukou encukou commented Mar 7, 2024

Before this change, ctypes classes used a custom dict subclass, StgDict,
as their tp_dict. This acts like a regular dict but also includes extra information
about the type.

This replaces stgdict by StgInfo, a C struct on the type, accessed by
PyObject_GetTypeData() (PEP-697).
All usage of StgDict (mainly variables named stgdict, dict, edict etc.) is
converted to StgInfo (named stginfo, info, einfo, etc.).
Where the dict is actually used for class attributes (as a regular PyDict), it's now
called attrdict.

This change -- not overriding tp_dict -- is made to make me comfortable with
the next part of this PR: moving the initialization logic from tp_new to tp_init.

The StgInfo is set up in __init__ of each class, with a guard that prevents
calling __init__ more than once. Note that abstract classes (like Array or
Structure) are created using PyType_FromMetaclass and do not have
__init__ called.
Previously, this was done in __new__, which also wasn't called for abstract
classes.
Since __init__ can be called from Python code or skipped, there is a tested
guard to ensure StgInfo is initialized exactly once before it's used.

Thanks to @neonene and @erlend-aasland.

encukou added 30 commits March 7, 2024 10:30
In this bootstrap commit, PyStgInfo is part of StgDictObject,
but that's a detail hidden by accessor functions.

(In the accessors, `state` is currently unused.)
Copy link
Member Author

@encukou encukou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old StgDict API was designed to be as fast as possible. Could we not just assert() that the result is >= 0 for these calls?

I'd like to think less about which functions can currently fail and which can't. Where possible, I'd like the to compiler figure that out for me.
Also, I don't want to micro-optimize without a benchmark. With branch prediction (and especially with LTO), I don't think the < 0 check is significant. I didn't see a significant change when running all of test_capi.

I did get a significant speedup by putting these functions in the header, and making them static inline. Let's do that. Incidentally, that'll probably also let the compiler figure out that the -1 doesn't happen.

We've had some segfaults caused by module access in tp_dealloc recently. We should make sure this is really safe.

I made it to leak rather than crash now. Let's leave a proper fix to another PR -- this can't be tested when the state is still global.


static PyType_Slot ctype_type_slots[] = {
{Py_tp_traverse, CType_Type_traverse},
{Py_tp_clear, CType_Type_clear},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would indeed be clearer! I'd prefer leaving it to a follow-up PR though: it'll be easier to debug when we get to isolating the module state.

Co-authored-by: Erlend E. Aasland <[email protected]>
Copy link
Contributor

@erlend-aasland erlend-aasland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did get a significant speedup by putting these functions in the header, and making them static inline. Let's do that. Incidentally, that'll probably also let the compiler figure out that the -1 doesn't happen.

Yes, putting the PEP-697 wrappers in the header is a neat solution!

I finished an initial read-through of _ctypes.c, and it looks good to me. I'll try to find the time to read through it a couple of times more.

Copy link
Contributor

@neonene neonene left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, too. Although CType_Type_dealloc() starts without a gc-untrack (against the docs?), I cannot produce a crash through type.__del__() when info->proto points a ctype class. It seems to me that the known crashes are related to object.__del__(): gh-2974, 35e1ff3.

@erlend-aasland
Copy link
Contributor

Impressive work; good job! Very inspiring.

@encukou encukou added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Mar 20, 2024
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @encukou for commit ab90768 🤖

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Mar 20, 2024
@encukou encukou merged commit dcaf33a into python:main Mar 20, 2024
@encukou encukou deleted the ctypes-no-stgdict branch March 20, 2024 16:33
@erlend-aasland
Copy link
Contributor

Yay! Well done!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants