Skip to content

gh-148510: restore func_version check in _LOAD_ATTR_PROPERTY_FRAME#148528

Merged
Fidget-Spinner merged 9 commits intopython:mainfrom
NekoAsakura:gh-148510/load-attr-property-frame
Apr 14, 2026
Merged

gh-148510: restore func_version check in _LOAD_ATTR_PROPERTY_FRAME#148528
Fidget-Spinner merged 9 commits intopython:mainfrom
NekoAsakura:gh-148510/load-attr-property-frame

Conversation

@NekoAsakura
Copy link
Copy Markdown
Contributor

@NekoAsakura NekoAsakura commented Apr 13, 2026

Do not merge.
Prerequisite: #148524 (Merged)
Mean +- std: [main] 12.8 ms +- 0.2 ms -> [branch] 12.3 ms +- 0.1 ms

Copy link
Copy Markdown
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

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

Just one thing that should be addressed. Thanks!

@NekoAsakura
Copy link
Copy Markdown
Contributor Author

Seems like probable_type gets dereferenced without a NULL check here.

if (probable_type->tp_version_tag == type_version && sym_set_type_version(owner, type_version)) {
// Promote the probable type version to a known one.
if ((probable_type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) == 0) {

@Fidget-Spinner
Copy link
Copy Markdown
Member

Seems like probable_type gets dereferenced without a NULL check here.

if (probable_type->tp_version_tag == type_version && sym_set_type_version(owner, type_version)) {
// Promote the probable type version to a known one.
if ((probable_type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE) == 0) {

Oops I think that was me. Can you please add a condition to that in another PR?

@NekoAsakura
Copy link
Copy Markdown
Contributor Author

Sure.

@Fidget-Spinner
Copy link
Copy Markdown
Member

@NekoAsakura sorry for the flip flop, but I think we can do this in this PR, I realised they're the same uop, just one is the other variant.

@NekoAsakura
Copy link
Copy Markdown
Contributor Author

NP! Wait me a second.

Copy link
Copy Markdown
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

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

Pretty close.

Comment on lines +246 to +248
sym_set_type_version(owner, type_version)) {
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)probable_type);
_Py_BloomFilter_Add(dependencies, probable_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.

Instead of setting the type verison, promote the type. Use sym_set_type(owner, probable_type) inside the if branch. Please update _GUARD_TYPE_VERSION_LOCKED to do that as well (and fix the NULL check).

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.

Wait I'm wrong here, we can't do that. I forgot what sym_set_type_version does. Sorry for misleading!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we can do both in sequence like:

sym_set_type(owner, probable_type);
sym_set_type_version(owner, type_version);

This promotes type while keeping the version.

Copy link
Copy Markdown
Member

@Fidget-Spinner Fidget-Spinner Apr 14, 2026

Choose a reason for hiding this comment

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

If we do sym_set_type(owner, probable_type);, we don't need to set the type version after, as sym_get_type_version just grabs tp_version_tag from the type. It's essentially a no-op.

Also type is strictly more information than type version in our framework https://github.com/python/cpython/blob/main/Python/optimizer_symbols.c#L37

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm afraid sym_get_type_version returns sym->cls.version for KNOWN_CLASS_TAG, not cls.type->tp_version_tag:

case JIT_SYM_KNOWN_CLASS_TAG:
return sym->cls.version;

And sym_set_type sets cls.version = 0 when promoting from RECORDED_TYPE_TAG:

case JIT_SYM_RECORDED_TYPE_TAG:
if (sym->recorded_type.type == typ) {
sym->tag = JIT_SYM_KNOWN_CLASS_TAG;
sym->cls.version = 0;
sym->cls.type = typ;

Without sym_set_type_version afterwards, the version stays 0 and guard elimination breaks.

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.

Ah ok. Thanks for digging into this!

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.

Is there a reason for it to return cls.version when class is known? It seems better to return the version from tp_version_tag.

Copy link
Copy Markdown
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM. Just one comment.

PyCodeObject *co = (PyCodeObject *)((PyFunctionObject *)fget)->func_code;
op(_LOAD_ATTR_PROPERTY_FRAME, (func_version/2, fget/4, owner -- new_frame)) {
if (sym_get_type_version(owner) == 0) {
ctx->done = true;
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.

Why not ctx->contradiction to true as well? That would abandon the whole trace, which is what we dant. Done allows the trace to continue executing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oops I accidentally deleted that. Thanks for catching it!

Comment on lines +4920 to +4921
# Check the optimizer traced through the property call.
self.assertIn("_CALL_BUILTIN_CLASS", 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.

No this doesn't check the optimizer actually ran inside the call. The easiest way is to check the global was promoted, ie the absence of _LOAD_GLOBAL_BUILTIN.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, was missing f->func = func on the abstract frame so globals couldn't be resolved. Sorted now, test asserts the absence of _LOAD_GLOBAL_BUILTINS.

Copy link
Copy Markdown
Member

@Fidget-Spinner Fidget-Spinner left a comment

Choose a reason for hiding this comment

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

Great!

@Fidget-Spinner Fidget-Spinner merged commit 52a7f1b into python:main Apr 14, 2026
74 checks passed
@NekoAsakura NekoAsakura deleted the gh-148510/load-attr-property-frame branch April 14, 2026 14:49
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