FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-131798: JIT: Further optimize `_CALL_ISINSTANCE` for class tuples by tomasr8 · Pull Request #134543 · python/cpython · GitHub

Repository navigation

gh-131798: JIT: Further optimize _CALL_ISINSTANCE for class tuples - #134543

Open
tomasr8 wants to merge 12 commits into
python:mainfrom
tomasr8:jit-tuple-isinstance
Open

tomasr8 wants to merge 12 commits into
python:mainfrom
tomasr8:jit-tuple-isinstance

Conversation

tomasr8 commented May 22, 2025 •
edited by bedevere-app Bot
Loading

Copy link
Copy Markdown
Member

We can already const eval isinstance(inst, cls) calls when both arguments are known, but only if cls is a single class (e.g. int).
This PR adds support for isinstance(inst, (cls1, cls2, ..., clsN)). This allows us to optimize for example:

  • isinstance(42, (int, str)) (to True)
  • isinstance(42, (bool, str)) (to False)

We can narrow to True even when only some of the classes are known, as long as inst is an instance of one of the known classes.

tomasr8 changed the title gh-134369: JIT: Further optimize _CALL_ISINSTANCE for class tuples gh-131798: JIT: Further optimize _CALL_ISINSTANCE for class tuples May 22, 2025
Comment on lines +2124 to +2125
self.assertIn("_BUILD_TUPLE", uops)
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

_BUILD_TUPLE is preventing us from optimizing out _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW.
The bytecode is basically:

LOAD_CONST
LOAD_CONST
_BUILD_TUPLE
_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW

To optimize this, we'd need some special handling for _BUILD_TUPLE in remove_unneeded_uops.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Got it, might be worth looking into next if you're up for it! Could be tricky, though.

Comment thread Python/optimizer_bytecodes.c Outdated
tomasr8 marked this pull request as ready for review May 22, 2025 22:34
tomasr8 requested a review from Fidget-Spinner as a code owner May 22, 2025 22:34
tomasr8 requested a review from brandtbucher May 22, 2025 22:34
Comment thread Python/optimizer_bytecodes.c Outdated

bedevere-app Bot commented Jul 1, 2025

Copy link
Copy Markdown

When you're done making the requested changes, leave the comment: I have made the requested changes; please review again.

tomasr8 commented Jul 19, 2025

Copy link
Copy Markdown
Member Author

I'll fix the PR hopefully this weekend.

So that was a lie 😆

Anyway, I think I addressed all your comments! :)

I have made the requested changes; please review again

bedevere-app Bot commented Jul 19, 2025

Copy link
Copy Markdown

Thanks for making the requested changes!

@brandtbucher: please review the changes made to this pull request.

bedevere-app Bot requested a review from brandtbucher July 19, 2025 09:38

brandtbucher left a comment

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Looks good, just a few suggestions:

Comment on lines +2124 to +2125
self.assertIn("_BUILD_TUPLE", uops)
self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops)

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Got it, might be worth looking into next if you're up for it! Could be tricky, though.

for _ in range(n):
# One of the classes is unknown, so we can't narrow
# to True or False, only bool
y = isinstance(42, (eval('str'), int))

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'm confused, why can't we narrow to True? We can't remove the call, but the result is known.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yep, that was a brainfart 😄 I somehow conflated narrowing to True/False with replacing the op, but we can obviously just narrow without removing the call as you said!

def testfunc(n):
x = 0
for _ in range(n):
y = isinstance(42, ())

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I had to pause and think for a second to figure out what this would even do. Nice edge case.

Comment thread Python/optimizer_bytecodes.c Outdated
// It could potentially define its own __instancecheck__
// method so we can only deduce bool.
out = NULL;
break;

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Could this be continue? We could still narrow to True later to handle cases like the one I called out above, right?

Suggested change
break;
continue;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Yup, see #134543 (comment). I just needed to add some extra bookkeeping to know when we can replace the call and when not.

}
PyTypeObject *cls_o = (PyTypeObject *)sym_get_const(ctx, item);
if (cls_o &&
sym_matches_type(item, &PyType_Type) &&

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Maybe add a comment explaining that this is to protect against metaclasses definine __instancecheck__.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

How would you formulate it? I don't think of it as specifically a guard for __instancecheck__ but basically PyObject_TypeCheck adapted to the JIT optimizer.

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We're not only checking that the object is a subclass of type, we're also checking that it is an exact instance of type itself. We care about this second condition because it guarantees that __instancecheck__ doesn't exist (otherwise we would need to look it up to check if it exists or not).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Thanks for the clarification, I added a comment :)

tomasr8 requested a review from brandtbucher July 29, 2025 18:59

Copy link
Copy Markdown
Member

Off-topic, but something I've been thinking about that you may want to check out next: removing bounds checks when indexing into strings and tuples with a constant index.

tomasr8 commented Aug 2, 2025

Copy link
Copy Markdown
Member Author

Thanks for the review! Super helpful as always :) I'll leave this open for a few days so @Fidget-Spinner can also have a look :)

Off-topic, but something I've been thinking about that you may want to check out next: removing bounds checks when indexing into strings and tuples with a constant index.

Sure, I can look into that! I guess this will also require knowing the string/tuple length?

if (cls_o &&
// Ensure that item is an exact instance of `type` ensuring that
// there is no __instancecheck__ defined.
sym_matches_type(item, &PyType_Type) &&

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Wait, wouldn't this only work on something that is isinstance(x, type), like the actual type itself, not any type like int? Sorry, I'm confused!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Hmm I don't believe so. If item is e.g. int then sym_matches_type(item, &PyType_Type) should return true

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

For example something like this seems to work:

JitOptRef ref_type = _Py_uop_sym_new_const(ctx, (PyObject *)&PyLong_Type);
TEST_PREDICATE(_Py_uop_sym_matches_type(ref_type, &PyType_Type), "int is not a type");

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

That''s kinda strange. The code for _Py_uop_sym_matches_type is:

_Py_uop_sym_get_type(sym) == typ

so it's a pointer comparison, not a subclass check.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

The subclass check is done after with PyType_IsSubtype if that's what you mean? The current version of _CALL_ISINSTANCE (which does not handle tuples) works the same way though. @brandtbucher, what do you think?

tomasr8 requested a review from markshannon as a code owner December 27, 2025 21:34

Copy link
Copy Markdown
Member

I'm really sorry Tomas, but the more I look at the code, the more I feel the complexity is not worth it in this case. __instancecheck__ is tricky (and you've handled that well in the code), but I feel for the additional lines we get in the optimizer_bytecodes.c, we don't get much back in terms of optimization :(.

tomasr8 closed this Dec 28, 2025
tomasr8 reopened this Jan 8, 2026

Fidget-Spinner left a comment

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.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

We need a test for a type with __instancecheck__, otherwise it LGTM, and you can merge after the test is added

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

github-actions Bot added the stale Stale PR or inactive for long period of time. label Apr 25, 2026
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

Labels

awaiting merge stale Stale PR or inactive for long period of time.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants


Back | FazBrowse Home | New Git URL