Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Why do we have two counters for function versions? #116916

Closed
gvanrossum opened this issue Mar 16, 2024 · 1 comment
Closed

Why do we have two counters for function versions? #116916

gvanrossum opened this issue Mar 16, 2024 · 1 comment
Assignees

Comments

@gvanrossum
Copy link
Member

gvanrossum commented Mar 16, 2024

In the interpreter state there are two separate counters that are used to generate unique versions for code and function objects. Both counters share the following properties:

  • The counter is an unsigned 32-bit int initialized to 1
  • When a new version is needed, the current value of the counter is used, and unless the counter is zero, the counter is incremented
  • When the counter overflows to 0, it remains zero, and no new versions can be allocated (in this case the specializer will just give up -- hopefully this never happens in real code, creating four billion code objects is just hard to imagine)

There are two ways that a function object obtains a version:

  • The usual path is when a function object is created by the MAKE_FUNCTION bytecode. In this case it gets the version from the code object. (See below.)
  • When an assignment is made to func.__code__ or a few other attributes, the function version is reset, and if the specializer later needs a function version, a new version number is taken from interp->func_state.next_version by _PyFunction_GetVersionForCurrentState().

Code objects obtain a version when they are created from interp->next_func_version.

I believe there's a scenario where two unrelated function objects could end up with the same version:

  • one function is created with a version derived from its code object, i.e. from interp->next_func_version;
  • the other has had its __code__ attribute modified, and then obtains a fresh version from interp->func_state.next_version, which somehow has the same value as the other counter had -- this should be possible because the counters are managed independently.

I looked a bit into the history, and it looks like in 3.11 there was only a static global next_func_version in funcobject.c, which was used by _PyFunction_GetVersionForCurrentState(). In 3.12 the current structure exists, except next_func_version is still a static global (func_state however is an interpreter member). It seems that this was introduced by gh-98525 (commit: fb713b2). Was it intentional to use two separate counters? If so, what was the rationale? (If it had to do with deepfreeze, that rationale is now gone, as we no longer use it.)

Linked PRs

@markshannon
Copy link
Member

I think there is no reason to have two function versions.
It is probably an accident resulting from adding versions to code objects at around the same time we moved the function version to the interpreter state.

However it came it about, we should remove one of the versions.

gvanrossum added a commit that referenced this issue Mar 18, 2024
Somehow we ended up with two separate counter variables tracking "the next function version".
Most likely this was a historical accident where an old branch was updated incorrectly.
This PR merges the two counters into a single one: `interp->func_state.next_version`.
vstinner pushed a commit to vstinner/cpython that referenced this issue Mar 20, 2024
…6918)

Somehow we ended up with two separate counter variables tracking "the next function version".
Most likely this was a historical accident where an old branch was updated incorrectly.
This PR merges the two counters into a single one: `interp->func_state.next_version`.
adorilson pushed a commit to adorilson/cpython that referenced this issue Mar 25, 2024
…6918)

Somehow we ended up with two separate counter variables tracking "the next function version".
Most likely this was a historical accident where an old branch was updated incorrectly.
This PR merges the two counters into a single one: `interp->func_state.next_version`.
diegorusso pushed a commit to diegorusso/cpython that referenced this issue Apr 17, 2024
…6918)

Somehow we ended up with two separate counter variables tracking "the next function version".
Most likely this was a historical accident where an old branch was updated incorrectly.
This PR merges the two counters into a single one: `interp->func_state.next_version`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants