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-118926: Deferred reference counting GC changes for free threading by Fidget-Spinner · Pull Request #121318 · python/cpython Conversation
|
|
!buildbot nogil |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
🤖 New build scheduled with the buildbot fleet by @Fidget-Spinner for commit 8cb139f 🤖 The command will test the builders whose names match following regular expression: The builders matched are:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
!buildbot nogil |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
🤖 New build scheduled with the buildbot fleet by @Fidget-Spinner for commit 700c2fd 🤖 The command will test the builders whose names match following regular expression: The builders matched are:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
!buildbot nogil |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
🤖 New build scheduled with the buildbot fleet by @Fidget-Spinner for commit cde931d 🤖 The command will test the builders whose names match following regular expression: The builders matched are:
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
One possibility to keep things moving for the free-threading build is to generate a different |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Ok I can port your old PR over to this one. I presume we're flushing the values to the stack before every escaping call? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@markshannon, writing all values to the stack is going to introduce an unnecessary performance regression. This is holding up the free-threaded work for what seems like small aesthetic complaints. If the concern is about maintainer confusion, we can add comments or otherwise document the dozen or so places where this is used. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
This is not just aesthetics. You say that "writing all values to the stack will by slow". Only for those parts of the stack that are in memory.
There are a hundred or more places where execution can escape from the interpreter, not counting Even if it is correct, it stills lays traps for the unwary. If this were changed to then it would be unsafe, yet there would be no warning or error from any tools. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
The rewritten
We should not be thinking about this in terms of where calls escape from the interpreter. We should be thinking about this in terms of where If you want to refactor |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
Sorry I forgot that this may introduce another perf regression on the free-threaded build, even if I limit it just to that. So I'm putting a pause on this. I think we should discuss this on Wednesday. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
In general, stackrefs must be spilled to the in-memory stack around any escaping call.
Identifying all escaping calls.We have a whitelist, all other calls are escaping. The list needs updating, but it's mostly right. Track assignments to output valuesAssignments are easy to spot It is easy enough to change the code move assignments out of branches, and don't want the code gen to have to do flow analysis. Generating spills around calls.Once we having identified the escaping call, we will need to walk backwards and forwards around the call to identify the whole statement. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
In case that sounds too expensive, don't worry. It shouldn't be. Spilling the stack pointer is cheap as registers often need to be spilled across calls anyway. Although escaping calls are common in |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
|
@markshannon, that is not what we discussed and agreed to
|
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
Tracking just I think what I proposed handles all the use cases, without a significant performance impact.
Why would anything get written multiple times? |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
I think the original goal was to not block TOS caching and full deferred refcounting. With the latest changes, this is now true. The added goal of laying the foundations for the two should be left as another PR. The responsibility of this PR IMO, is to not burden stack caching and full deferred refcounting (which it should have achieved with the latest commit). Supporting more features is out of scope. |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@markshannon - I'm most concerned because I thought we were on the same page on this approach after our last meeting. I don't think starting with this approach precludes future changes, like what you've outlined above. This PR is a bottleneck for a lot of the free-threading deferred reference counting work, so I'd appreciate it if we can figure out how to unblock it. |
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.
Having the spilling controlled by the code generator is a major improvement.
Thanks for doing that.
I have a few comments
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| PyObject **items = _PyTuple_ITEMS(seq_o); | ||
| for (int i = oparg; --i >= 0; ) { | ||
| *values++ = PyStackRef_FromPyObjectNew(items[i]); | ||
| #ifdef Py_GIL_DISABLED /* flush specials */ |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Don't emit anything if there are no values to flush.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| emit_to(out, tkn_iter, "SEMI") | ||
| out.emit(";\n") | ||
| target = uop.body.index(tkn) | ||
| assert uop.body[target-1].kind == "EQUALS", (f"{uop.name} Result of a specials that is flushed" |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
This is going to crash as soon as anyone writes anything but an assignment in front of PyStackRef_FromPyObjectNew.
We might want to insist that the result of PyStackRef_FromPyObjectNew is assigned to a variable, maybe only an output variable, but that should be an error not a crash.
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.
I've updated the error handling. For the most part, PyStackRef_FromPyObjectNew is now required to be assigned to an output variable, but we also allow assignment to a dereferenced pointer due to usages like:
frame->localsplus[offset + i] = PyStackRef_FromPyObjectNew(o); (in COPY_FREE_VARS)*values++ = PyStackRef_FromPyObjectNew(items[i]) (in UNPACK_SEQUENCE_LIST)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| #ifdef Py_GIL_DISABLED /* flush specials */ | ||
| stack_pointer[-2 - oparg] = method; | ||
| #endif /* flush specials */ | ||
| stack_pointer[-2 - oparg] = method; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Why is the same value being saved twice?
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.
I updated the generator to avoid saving the same value twice. I got rid of the #ifdef Py_GIL_DISABLED guards because I think they would add too much complexity for no real gain.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| if not uop.properties.ends_with_eval_breaker: | ||
| out.emit_at("CHECK_EVAL_BREAKER();", tkn) | ||
|
|
||
| def replace_pystackref_frompyobjectnew( |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
I don't think that locally replacing PyStackRef_FromPyObjectNew is sufficient in general. It ensures that the references are in memory, but it doesn't ensure that the stack_pointer is saved, so the references might not be visible.
IIRC this is safe for the current FT implementation because you NULL out the unused part of the stack.
Could you add a comment explaining why this is safe for FT even though it doesn't appear to be.
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.
I added a comment
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| target = uop.body.index(tkn) | ||
| assert uop.body[target-1].kind == "EQUALS", (f"{uop.name} Result of a specials that is flushed" | ||
| " must be written to a stack variable directly") | ||
| # Scan to look for the first thing it's assigned to |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
FYI, this sort of analysis should ideally be performed in the analysis phase, not in the code generation phase.
Don't worry about it for now.
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.
I've moved this to analyzer.py since it required a number of changes anyways.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| found_valid_assignment = assgn_target.text | ||
| break | ||
|
|
||
| if found_valid_assignment: |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
What happens if we haven't found a valid assignment?
That sounds like an error.
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.
This now raises an analysis_error.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| self.peek_offset.clear() | ||
| out.start_line() | ||
|
|
||
| def write_variable_to_stack(self, out: CWriter, var_name: str, cast_type: str = "uintptr_t", extract_bits: bool = False) -> None: |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
It seems odd to be writing individual values to the stack. If any value on the stack needs to be in memory, don't they all?
Would it make more sense to use the flush method?
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.
I'm looking at this since Ken is on vacation.
I think that the logic in this PR (after resolving merge conflicts with main) no longer works after #122286.
Before #122286, the tier1 generator had the outputs on the logical stack when emit_tokens() is called. Now, the inputs are still on the logical stack when emit_tokens is called and the outputs are only pushed after emit_tokens().
I think flush() has the same issue: the logical stack still contains the inputs, not the outputs.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
When you're done making the requested changes, leave the comment: |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
|
@markshannon, I've split off the changes to the interpreter generator into a separate PR: I think that will make it easier to see the changes to the generated code. (And the merge conflicts were getting unwieldy.) |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
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.