Skip to content

Conversation

@reidenong
Copy link
Contributor

@reidenong reidenong commented Jan 28, 2026

  1. Implements unique reference tracking to references in the JIT Optimizer.
  2. Uses the unique reference tracking to eliminate unnecessary reference counting for uniquely referenced tuples. ex:
x, y, z = f()

Some notes:

  • Only implements the unique reference tracking (and relevant optimization) for tuples in _BUILD_TUPLE, to be extended to other objects in the future
  • Objects are deemed as not uniquely referenced when they are made aliases or duplicated, in uops of _LOAD_FAST_* and _COPY.

Would appreciate any feedback.
Thanks

@reidenong
Copy link
Contributor Author

reidenong commented Jan 28, 2026

Discussed with @Fidget-Spinner, will reopen the PR after implementing PyStackRef_CLOSE_SPECIALIZED for _UNPACK_SEQUENCE_UNIQUE_TUPLE

@reidenong reidenong marked this pull request as draft January 28, 2026 18:38
@reidenong reidenong marked this pull request as ready for review January 29, 2026 11:04
@Fidget-Spinner
Copy link
Member

Windows Ci failure looks possibly related, can you please look into it?

@reidenong reidenong marked this pull request as draft January 31, 2026 06:39
@reidenong reidenong marked this pull request as ready for review January 31, 2026 16:36
Copy link
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.

This is excellent. Just 2 comments, then I think it's safe to merge.


op(_COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) {
assert(oparg > 0);
bottom = PyJitRef_IsUnique(bottom) ? PyJitRef_StripReferenceInfo(bottom) : bottom;
Copy link
Member

Choose a reason for hiding this comment

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

We only need to strip unique info here, not the borrow info too. This way we still get refcount elimination with the borrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is consistent with the code?

If the ref is unique, we remove the unique tag, otherwise we leave it as is. Since a ref cannot be both unique and borrowed at the same time, borrowed/invalid refs will be maintained.

Copy link
Member

Choose a reason for hiding this comment

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

Sounds good, thanks!

Copy link
Member

@markshannon markshannon left a comment

Choose a reason for hiding this comment

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

We need to be very careful about tracking the uniqueness of a reference.
Uniqueness of a reference is a property of the whole VM, if we miss any operation that can duplicate a reference, we could introduce a serious bug.

With that in mind, I'd recommend only tracking references that remain on the stack, as it is too easy to make new references to objects stored in local variables:

  • Through away the uniqueness in STORE_FAST
  • Assert that references are not unique in LOAD_FAST[_BORROW]

Other than that, I think the analysis is sound.

This could be considerable more efficient, see comments below:

DECREF_INPUTS();
}

op(_UNPACK_SEQUENCE_UNIQUE_TUPLE, (seq -- values[oparg])) {
Copy link
Member

Choose a reason for hiding this comment

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

This is might end up being less efficient than _UNPACK_SEQUENCE_TWO_TUPLE due to the looping and having to spill the values to memory.

I think it is worth making two variants of this for 2 and 3 length tuples.

PyObject **items = _PyTuple_ITEMS(seq_o);
for (int i = oparg; --i >= 0; ) {
*values++ = PyStackRef_FromPyObjectSteal(items[i]);
items[i] = NULL;
Copy link
Member

Choose a reason for hiding this comment

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

If we free the tuple directly, we don't need to NULL out the fields.

PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
assert(PyTuple_CheckExact(seq_o));
assert(PyTuple_GET_SIZE(seq_o) == oparg);
DEOPT_IF(!_PyObject_IsUniquelyReferenced(seq_o));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
DEOPT_IF(!_PyObject_IsUniquelyReferenced(seq_o));
assert(_PyObject_IsUniquelyReferenced(seq_o));

If our analysis is correct, we don't need a runtime check.

*values++ = PyStackRef_FromPyObjectSteal(items[i]);
items[i] = NULL;
}
PyStackRef_CLOSE_SPECIALIZED(seq, _PyTuple_EmptyExactDealloc);
Copy link
Member

@markshannon markshannon Feb 2, 2026

Choose a reason for hiding this comment

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

Suggested change
PyStackRef_CLOSE_SPECIALIZED(seq, _PyTuple_EmptyExactDealloc);
PyObject_GC_UnTrack(seq_o);
_PyTuple_Free(seq_o);

/* Methods */

void
_PyTuple_EmptyExactDealloc(PyObject *obj)
Copy link
Member

Choose a reason for hiding this comment

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

Rather than a deallocing the tuple, we just need to free it.

Something like _PyTuple_Free.
Just assert that Py_SIZE(op) != 0 and free the tuple.

}

op(_UNPACK_SEQUENCE_UNIQUE_TUPLE, (seq -- values[oparg])) {
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
PyObject *seq_o = PyStackRef_AsPyObjectBorrow(seq);
PyObject *seq_o = PyStackRef_AsPyObjectSteal(seq);

We want to free the tuple directly, so we need to steal the reference.

@bedevere-app
Copy link

bedevere-app bot commented Feb 2, 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.

And if you don't make the requested changes, you will be put in the comfy chair!

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