GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — gh-131798: JIT: Narrow the return type of `isinstance` for some known arguments by tomasr8 · Pull Request #133172 · python/cpython Conversation
|
| // isinstance(obj, cls) where both obj and cls have known types | ||
| // We can deduce either True or False | ||
| PyTypeObject *inst_type = sym_get_type(inst_sym); | ||
| if (sym_matches_type(inst_sym, cls) || PyType_IsSubtype(inst_type, cls)) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This simulates PyObject_TypeCheck
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment to that effect? :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Added!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
|
|
||
| op(_CALL_ISINSTANCE, (callable, self_or_null, args[oparg] -- res)) { | ||
| if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I've seen this guard used elsewhere with self_or_null but it's not clear to me whether it is needed here as well?
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Yep. It's just a (weird) way of saying "we know whether it's NULL or not". Maybe it's worth adding a helper function (in another PR) to make it clearer, since the meaning is super subtle.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Cool! Can you also add a test where the second isinstance arg is a class with a metaclass defining __instancecheck__?
class EvenNumberMeta(type): def __instancecheck__(self, number): return not number % 2 class EvenNumber(metaclass=EvenNumberMeta): pass # Optimizer only narrows to bool, runtime value is True: even = isinstance(42, EvenNumber)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| class Foo: | ||
| bar = 42 | ||
|
|
||
| x = 0 | ||
| for _ in range(n): | ||
| # we only know bar (LOAD_ATTR) is not null (set via sym_new_not_null) | ||
| bar = Foo.bar | ||
| # This will only narrow to bool and not to True due to 'bar' having | ||
| # unknown (non-null) type | ||
| y = isinstance(bar, int) | ||
| if y: | ||
| x += 1 | ||
| return x |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is pretty fragile: the class attr lookup is cached in the bytecode, and I actually have a branch I'll open a PR with soon that teaches the optimizer to read these caches.
So instead, let's use everyone's favorite optimization-breaker:
| class Foo: | |
| bar = 42 | |
| x = 0 | |
| for _ in range(n): | |
| # we only know bar (LOAD_ATTR) is not null (set via sym_new_not_null) | |
| bar = Foo.bar | |
| # This will only narrow to bool and not to True due to 'bar' having | |
| # unknown (non-null) type | |
| y = isinstance(bar, int) | |
| if y: | |
| x += 1 | |
| return x | |
| x = 0 | |
| for _ in range(n): | |
| # The optimizer doesn't know the return type here: | |
| bar = eval("42") | |
| # This will only narrow to bool: | |
| y = isinstance(bar, int) | |
| if y: | |
| x += 1 | |
| return x |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Nice trick to use eval! I updated the test :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| } | ||
|
|
||
| op(_CALL_ISINSTANCE, (callable, self_or_null, args[oparg] -- res)) { | ||
| if (sym_is_null(self_or_null) || sym_is_not_null(self_or_null)) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Yep. It's just a (weird) way of saying "we know whether it's NULL or not". Maybe it's worth adding a helper function (in another PR) to make it clearer, since the meaning is super subtle.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
| // isinstance(obj, cls) where both obj and cls have known types | ||
| // We can deduce either True or False | ||
| PyTypeObject *inst_type = sym_get_type(inst_sym); | ||
| if (sym_matches_type(inst_sym, cls) || PyType_IsSubtype(inst_type, cls)) { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Can you add a comment to that effect? :)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| } | ||
| else { | ||
| // isinstance(obj, cls) where obj has unknown type | ||
| res = sym_new_type(ctx, &PyBool_Type); | ||
| } | ||
| } | ||
| else { | ||
| // isinstance(obj, cls) where cls has unknown type | ||
| res = sym_new_type(ctx, &PyBool_Type); | ||
| } | ||
| } | ||
| else { | ||
| res = sym_new_type(ctx, &PyBool_Type); | ||
| } | ||
| } |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
You can avoid all of this repetition by doing an unconditional res = sym_new_type(ctx, &PyBool_Type); at the top, and using sym_set_const(ctx, ...) to narrow it when possible.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Yup that is way better, updated!
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
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 |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
(Marking as draft until #133339 is merged which will let us simplify the oparg logic) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I think I addressed all your points. I also added the test you suggested. I'm planning to add support for tuples (e.g. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Just one suggestion, then we can land it.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Co-authored-by: Brandt Bucher <[email protected]>
|
This LGTM. A side note: on PyPy, they can actually narrow |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Yeah, could be cool. It's not really useful to us right now, since all of our guards are against exact types/versions, not many subclass checks. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
CI failures are unrelated. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…arguments in the JIT (pythonGH-133172)
…arguments in the JIT (pythonGH-133172)
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.