Skip to content

gh-149180: Avoid double checking tp_as_number, tp_as_sequence, tp_as_mapping#149317

Open
anujbharambe wants to merge 4 commits intopython:mainfrom
anujbharambe:gh-149180-empty-tp-as-structs
Open

gh-149180: Avoid double checking tp_as_number, tp_as_sequence, tp_as_mapping#149317
anujbharambe wants to merge 4 commits intopython:mainfrom
anujbharambe:gh-149180-empty-tp-as-structs

Conversation

@anujbharambe
Copy link
Copy Markdown
Contributor

gh-149180: Avoid double checking tp_as_number, tp_as_sequence, tp_as_mapping

During PyType_Ready, assign NULL tp_as_number, tp_as_sequence, and tp_as_mapping pointers to shared static empty (all-zero) structs. After initialization, these three fields are guaranteed non-NULL for all ready types.

This eliminates the need for callers to double-check — first the struct pointer, then the slot within — e.g.:

// Before: two checks needed
if (Py_TYPE(s)->tp_as_mapping && Py_TYPE(s)->tp_as_mapping->mp_length)

// After: only the slot check is needed (callsite cleanup is a follow-up)
if (Py_TYPE(s)->tp_as_mapping->mp_length)

Changes

Objects/typeobject.c

  • Added three static zero-initialized empty structs (_Py_empty_number_methods, _Py_empty_sequence_methods, _Py_empty_mapping_methods).
  • In type_ready_inherit(), after type_ready_inherit_as_structs(), assign the empty structs as fallbacks when tp_as_number, tp_as_sequence, or tp_as_mapping is still NULL. Placed outside the if (base != NULL) block so it covers PyBaseObject_Type.
  • Added CHECK() assertions in _PyType_CheckConsistency() to enforce the non-NULL invariant for ready types.
  • Added basebase == NULL guards in inherit_slots() before dereferencing basebase->tp_as_number, basebase->tp_as_sequence, and basebase->tp_as_mapping. Previously these were unreachable because the outer if checked base->tp_as_number != NULL which was false for object. Now that the empty struct makes it non-NULL, base->tp_base (NULL for object) must be guarded.

Objects/abstract.c

  • In PyNumber_InPlaceMultiply, changed else if (mw != NULL) to if (mw != NULL) in the sequence-repeat fallback. Previously int's tp_as_sequence was NULL, so the first branch was skipped and the else if handled the right operand (e.g. 2 *= [1]). Now that int has a non-NULL empty tp_as_sequence, the first branch is entered but finds no slots; removing the else allows the second branch to still run.

Safety

  • inherit_slots(): Runs before our fallback. The basebase NULL guards preserve existing behavior — all slots in the empty struct are NULL, so SLOTDEFINED returns false.
  • Heap types: Already have non-NULL tp_as_* (set by type_new_alloc()). The fallback never triggers.
  • Existing tp_as_* != NULL guards at callsites: Become always-true for ready types, which is harmless. The inner slot check still correctly fails via the empty struct's NULL slots.

cc- @markshannon

…tp_as_mapping

During PyType_Ready, assign NULL tp_as_number, tp_as_sequence, and
tp_as_mapping pointers to shared static empty (all-zero) structs.
After initialization these three fields are guaranteed non-NULL for
all ready types.

Also fix inherit_slots() basebase NULL dereference and
PyNumber_InPlaceMultiply else-if fallthrough exposed by the change.
@read-the-docs-community
Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #32514421 | 📁 Comparing 3f51f2d against main (8a7edda)

  🔍 Preview build  

1 file changed
± whatsnew/changelog.html

Comment thread Objects/abstract.c
if (f != NULL)
return sequence_repeat(f, v, w);
}
else if (mw != NULL) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand this branch change.

@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented May 3, 2026

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Comment thread Objects/typeobject.c
Comment on lines +9162 to +9164
static PyNumberMethods _Py_empty_number_methods = {0};
static PySequenceMethods _Py_empty_sequence_methods = {0};
static PyMappingMethods _Py_empty_mapping_methods = {0};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Those structs can be declared constants as well.

Comment thread Objects/typeobject.c
if (type->tp_as_mapping != NULL && base->tp_as_mapping != NULL) {
basebase = base->tp_base;
if (basebase->tp_as_mapping == NULL)
if (basebase == NULL || basebase->tp_as_mapping == NULL)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When is it possible for basebase to be NULL?

Copy link
Copy Markdown
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

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

Please make some benchmarks as well to see how much we gain (probably macro benchmarks from pyperformance for that).

I think there are other places where we have LHS/RHS being tested so it may require more than just the change in PyNumber_Multiply (e.g., C classes that have implement their own slots).

AFAIU, all tp_as_sequence are no more NULLs for PyNumber_InPlaceMultiply or am I wrong here?

@picnixz
Copy link
Copy Markdown
Member

picnixz commented May 3, 2026

Also, don't use LLMs for your PRs unless you at least disclose its usage and to understand what has been written by you or it. See our policy: https://devguide.python.org/getting-started/generative-ai/

Copy link
Copy Markdown
Contributor

@NekoAsakura NekoAsakura left a comment

Choose a reason for hiding this comment

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

Please read requirements in the issue carefully.

Comment thread Objects/abstract.c
return sequence_repeat(f, v, w);
}
else if (mw != NULL) {
if (mw != NULL) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This would remove the need for the extra check

This is a dead check, exactly the kind of "extra check" the issue is asking to remove. There's a fair bit more like it that's been left in.

Comment thread Objects/typeobject.c
Comment on lines +9162 to +9164
static PyNumberMethods _Py_empty_number_methods = {0};
static PySequenceMethods _Py_empty_sequence_methods = {0};
static PyMappingMethods _Py_empty_mapping_methods = {0};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Immutable types can all share common constant structs, so this should use very little extra memory.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants