Skip to content

[mypyc] Move non-captured generator state to private frames - #21991

Merged
JukkaL merged 7 commits into
masterfrom
mypyc-async-5
Sep 15, 2026
Merged

JukkaL merged 7 commits into
masterfrom
mypyc-async-5

Conversation

@JukkaL

@JukkaL JukkaL commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Some generator-owned persistent state previously lived in the closure environment whenever a generator needed a separate environment class, even when nested functions could not access that state. This was potentially quite inefficient.

Move non-captured arguments, nested-function objects, and a small subset of locals to the private generator frame. Most non-argument locals were already stored there.

This mainly affects generators and coroutines that contain a nested function or a lambda, or are themselves nested. Simple generators whose frame and environment were already merged are unchanged.

Performance impact:

On free-threaded builds, moved fields use non-atomic, borrow-friendly attribute accesses instead of atomic environment accesses, avoiding relatively slow synchronized operations and increfs/decrefs. Captured variables remain in the shared environment and continue to use atomic access.

GIL builds also benefit from borrowing frame reads. Savings in a generator method that accessed self nine times and contained a nested helper:

  • Increfs/decrefs dropped from 22/37 to 10/25.
  • Environment dereferences dropped from 17 to 2.
  • The result was within one reference-count pair of the equivalent method without a nested helper.

Persistent generator bindings previously lived in the closure environment even when no nested function could access them. This made private generator state part of a potentially shared object and prevented private-frame optimizations from applying to it.

Keep captured bindings in the closure environment, but put noncaptured arguments, locals, and nested-function references on the private generator frame. Apply the same routing to deleted error-overlap locals, which need attribute storage eagerly for their definedness bitmap.

Update nested environment links to follow the selected storage object and add coverage for mixed captured and private state.
Generator locals stored as attributes exposed their internal generated names in invalid-deletion diagnostics.

Strip the generated attribute prefix when reporting an invalid deletion so the diagnostic names the source variable instead of its internal storage slot.
Generator arguments were processed by add_args_to_env and then again by add_args_to_generator_frame at both generator setup sites. Each pass walked every argument and repeated the generator-environment classification; the environment pass also computed bitmap argument counts that it did not use.

Consolidate both paths in add_generator_args, which assigns each argument to either the closure environment or the private generator frame in one pass. Have environment finalization expose its instance without independently adding generator arguments. This preserves the existing storage and lifetime rules while removing duplicate IR-building work.
Source-named slots on generator frames that can be inherited may collide
with slots introduced by another frame class. Give private frame slots a
class-qualified prefix so each declaring class has its own namespace.

Use a distinct prefix for generator-frame and closure-environment storage,
and teach generated diagnostics to recover the original source name from
either form.
The generator frame split left several redundant routing checks behind. Generator arguments no longer pass through add_args_to_env, variables listed in a function's free-variable set are already known to require environment storage, and only nested function objects can still choose between the closure environment and private generator frame.

Rename the routing predicate to describe capture instead of its storage destination, use it for both generator and ordinary-function lifetime decisions, and remove the add_vars_to_env helper closure. Also state the separate-environment invariant directly and undo ordering and type-assertion leftovers from an abandoned completed-generator optimization.
Final generator frame classes cannot be inherited and are also the classes eligible for merging with their environments, so their private source-named attributes cannot collide with inherited or closure attributes.

Use the plain generator-frame prefix for final classes while retaining class qualification for non-final frames. Continue recognizing both forms when recovering source names for diagnostics.
Comment on lines +2215 to 2216
del value # E: "value" cannot be deleted \
# N: Using "__deletable__ = ['<attr>']" in the class body enables "del obj.<attr>"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

i think it'd make sense to keep the TODO comment because the note doesn't apply in this case.

@JukkaL
JukkaL merged commit 29de3de into master Sep 15, 2026
18 checks passed
@JukkaL
JukkaL deleted the mypyc-async-5 branch September 15, 2026 20:00
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

Successfully merging this pull request may close these issues.

2 participants