Repository navigation
Conversation
…e object The compiler merges equal constants of a module through its constant cache. intern_constants() then ran on each code object and rebuilt a frozenset (or, in the free-threaded build, a slice) when one of its strings was replaced by an already interned one, so a constant shared by several functions was copied for some of them depending on which strings happened to be interned in the process. The class body's __qualname__ constant and co_qualname had the same problem. marshal then wrote different bytes for the same source. Intern the eligible strings of the final constants of each code unit after optimization and before the code object is created. Tuples are updated in place; a frozenset or slice is rebuilt once and stored in the constant cache so that later units get the same object. The same code is used in both builds. Which strings are interned does not change, except that identifier-like strings in slice constants are now interned in the default build too. TestConstantsIdentity compares the identity graph and the marshal output of code compiled in differently prepared processes, including names tuples shared with constants.
|
🤖 New build scheduled with the buildbot fleet by @gpshead for commit badcae7 🤖 Results will be shown at: https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F159193%2Fmerge If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Compiling the same source could give code objects whose constants are shared differently, depending on which strings happened to be interned in the process.
marshalthen writes different bytes, so.pycfiles were not reproducible (gh-129724).Example
Two functions use an equal set constant. The compiler makes it one
frozensetobject, unless a string in it is already interned when the code objects are created:With this PR all four are 398 bytes with one shared frozenset. The same happened to the
__qualname__constant of a class body: compilingclass A: class B: passtwice while the first result is alive gives different bytes on main.Cause and fix
The compiler merges equal constants of a module through its constant cache. Interning then ran later, once per code object, in
intern_constants(). A frozenset cannot be modified, so when one of its strings had to be replaced by an already interned string, that code object got a private copy.flowchart TB subgraph before["main"] direction TB m1["compiler constant cache<br>one frozenset F for f and g"] --> m2["code object f<br>intern_constants()"] m1 --> m3["code object g<br>intern_constants()"] m2 -- "a string of F is<br>already interned" --> m4["F' = copy of F<br>with the interned string"] m3 -- "same" --> m5["F'' = another copy"] m4 --> m6["f and g no longer share:<br>marshal writes the set twice"] m5 --> m6 end subgraph after["this PR"] direction TB n1["compiler constant cache<br>one frozenset F for f and g"] --> n2["intern_final_constants()<br>when f is assembled"] n2 -- "a string of F is<br>already interned" --> n3["F' built once and<br>stored in the cache entry of F"] n3 --> n4["g is assembled:<br>the cache gives F'"] n4 --> n5["f and g share F'<br>whatever was interned before"] n5 --> n6["intern_constants() in<br>the code object: nothing<br>left to replace"] endintern_final_constants()runs inoptimize_and_assemble_code_unit(), after the optimizer and before the code object is created, on the constants the unit really ends up with. Strings that never reach a code object are not interned.intern_constants()inObjects/codeobject.cis not changed; itsshould_intern_string()is renamed_PyCode_ShouldInternString()and exported so the compiler uses the same rule.Behaviour changes
sliceconstants are now interned on the default build too, as they already were for every other kind of constant.compile()is about 0.5% slower on the default build (release, not PGO), which is inside the run-to-run noise.Tests
TestConstantsIdentityintest_compilecompiles a table of cases in child processes prepared in different ways (strings pre-interned, used as names, used as constants, the same source already compiled) and compares both the identity graph of the constants and the marshal output with a fresh process. It also covers tuples of names that are the same object as an equal constant (co_namesof methods using a mangled name, keyword names,fromlists,__static_attributes__), so that a change which re-creates those tuples per code object fails the test (the current version of draft #122180 does this).With the marshal PR gh-159192 fixing the other half of gh-129724
Compiling each stdlib file under 14 different process conditions (file order, threads, pre-interned strings, earlier imports, hash seeds, keeping code objects alive) and comparing with one file per process:
The matrix was only run with all of these applied together. The three other fixes do not affect code compiled from source strings, which is all this matrix compiles.
drafted using Claude, reviewed & iterated upon by me.
(combined with the marshal fix, this will close the title issue)