Uh oh!
There was an error while loading. Please reload this page.
gh-131798: JIT: Further optimize _CALL_ISINSTANCE for class tuples - #134543
gh-131798: JIT: Further optimize _CALL_ISINSTANCE for class tuples#134543tomasr8 wants to merge 12 commits into
_CALL_ISINSTANCE for class tuples#134543Conversation
_CALL_ISINSTANCE for class tuples_CALL_ISINSTANCE for class tuples| self.assertIn("_BUILD_TUPLE", uops) | ||
| self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) |
There was a problem hiding this comment.
_BUILD_TUPLE is preventing us from optimizing out _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW.
The bytecode is basically:
LOAD_CONSTLOAD_CONST_BUILD_TUPLE_POP_CALL_TWO_LOAD_CONST_INLINE_BORROWTo optimize this, we'd need some special handling for _BUILD_TUPLE in remove_unneeded_uops.
There was a problem hiding this comment.
Got it, might be worth looking into next if you're up for it! Could be tricky, though.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
When you're done making the requested changes, leave the comment: |
tomasr8
commented
Jul 19, 2025
So that was a lie 😆 Anyway, I think I addressed all your comments! :) I have made the requested changes; please review again |
Thanks for making the requested changes! @brandtbucher: please review the changes made to this pull request. |
brandtbucher
left a comment
There was a problem hiding this comment.
Looks good, just a few suggestions:
| self.assertIn("_BUILD_TUPLE", uops) | ||
| self.assertIn("_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW", uops) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
I'm confused, why can't we narrow to True? We can't remove the call, but the result is known.
There was a problem hiding this comment.
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, ()) |
There was a problem hiding this comment.
I had to pause and think for a second to figure out what this would even do. Nice edge case.
| // It could potentially define its own __instancecheck__ | ||
| // method so we can only deduce bool. | ||
| out = NULL; | ||
| break; |
There was a problem hiding this comment.
Could this be continue? We could still narrow to True later to handle cases like the one I called out above, right?
| break; | |
| continue; |
There was a problem hiding this comment.
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) && |
There was a problem hiding this comment.
Maybe add a comment explaining that this is to protect against metaclasses definine __instancecheck__.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Thanks for the clarification, I added a comment :)
brandtbucher
commented
Jul 29, 2025
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
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 :)
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) && |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Hmm I don't believe so. If item is e.g. int then sym_matches_type(item, &PyType_Type) should return true
There was a problem hiding this comment.
For example something like this seems to work:
JitOptRefref_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");There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
Fidget-Spinner
commented
Dec 28, 2025
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. |
Fidget-Spinner
left a comment
There was a problem hiding this comment.
We need a test for a type with __instancecheck__, otherwise it LGTM, and you can merge after the test is added
This PR is stale because it has been open for 30 days with no activity. |
We can already const eval
isinstance(inst, cls)calls when both arguments are known, but only ifclsis 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))(toTrue)isinstance(42, (bool, str))(toFalse)We can narrow to
Trueeven when only some of the classes are known, as long asinstis an instance of one of the known classes.