GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-139922: Tail calling for MSVC (VS 2026) by Fidget-Spinner · Pull Request #139962 · python/cpython

Conversation

@Fidget-Spinner

@Fidget-Spinner Fidget-Spinner commented Oct 11, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

I know it isn't good practice to bundle multiple changes into the same PR, but this PR does two things which are required to get tail calling working on MSVC and which I think are beneficial.

  1. Move the _PyStackRef off a per-ceval allocated buffer into a per-thread one. This actually reduces the C stack consumption of ceval.c. I had planned this for some time (see Move stackref buffer to per-eval loop to reduce interp stack usage #138115), but we also need this to let MSVC know the variables are fine to outlive their current scope. This may be a tiny bit slower versus allocating it on the stack, but it removes the performance cliff of having any function calling more than 10 arguments suddenly getting a lot slower due to having to allocate memory. Overall, I think this is a better design choice for the two reasons of less C stack consumption and allowing longer calls.
  2. Add __restrict to correct places and scoping where needed to tell MSVC that a local variable does not outlive the scope. We only use this for MSVC for now as other compilers have different qualifier rules. However, it's definitely possible to extend this to clang and gcc to let them benefit in the future.

There is no noticeable perf drop on a nogil benchmark for the normal interpreter https://github.com/facebookexperimental/free-threading-benchmarking/tree/main/results/bm-20251011-3.15.0a0-0786133-NOGIL

  • Issue: Tail calling interpreter for MSVC #139922

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Fidget-Spinner and others added 7 commits August 21, 2025 22:20
@Fidget-Spinner @brandtbucher
Brandt's changes
82d1259
Co-Authored-By: Brandt Bucher <[email protected]>
@Fidget-Spinner @HulonJenkins
Hulon's and mine changes
3248658
Co-Authored-By: Hulon Jenkins <[email protected]>
@Fidget-Spinner
push stackrefs into tstate
9ac430d
@Fidget-Spinner
Fix last few remaining problems
085c1d7
@Fidget-Spinner
fix handling of recursion
0b12f2e
@Fidget-Spinner @chris-eibl
Changes by Chris to support clang-cl
acf48f5
Co-Authored-By: Chris Eibl <[email protected]>
@Fidget-Spinner
Make the restrict MSVC only for now
35e96c1
@Fidget-Spinner
Fidget-Spinner requested review from a team, ZeroIntensity, ericsnowcurrently, markshannon and methane as code owners October 11, 2025 16:45
@bedevere-app bedevere-app Bot added the awaiting core review label Oct 11, 2025
@bedevere-app bedevere-app Bot mentioned this pull request Oct 11, 2025
Tail calling interpreter for MSVC #139922
Closed
Fidget-Spinner and others added 7 commits October 11, 2025 17:47
@Fidget-Spinner
Merge remote-tracking branch 'upstream/main' into msvc-tailcall-take-two
d7737e9
@Fidget-Spinner
Change restricts to MSVC only
86f19cf
@blurb-it
📜🤖 Added by blurb_it.
40013cc
@Fidget-Spinner
Reduce diff
48db59e
@Fidget-Spinner
Merge branch 'msvc-tailcall-take-two' of github.com:Fidget-Spinner/cp…
bc9d23c
…ython into msvc-tailcall-take-two
@Fidget-Spinner
Work around specialization
19e02c2
@Fidget-Spinner
Fix VS 2026
66ec774
ZeroIntensity
ZeroIntensity reviewed Oct 11, 2025
View reviewed changes
Comment thread Include/pyport.h Resolved

Uh oh!

There was an error while loading. Please reload this page.

Fidget-Spinner added 3 commits October 12, 2025 02:27
@Fidget-Spinner
fix a typo
50f8ff7
@Fidget-Spinner
Move to macros to internal header
5d908b4
@Fidget-Spinner
Merge branch 'msvc-tailcall-take-two' of github.com:Fidget-Spinner/cp…
0786133
…ython into msvc-tailcall-take-two
zooba
zooba reviewed Oct 13, 2025
View reviewed changes

@zooba zooba left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Very neat, looks good to me, but don't let me be the final reviewer on this.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Include/internal/pycore_tstate.h
#endif

/* How much scratch space to give stackref to PyObject* conversion. */
#define MAX_STACKREF_SCRATCH 1024

@zooba zooba Oct 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

How arbitrary is this amount? What is the usual amount of space required for conversions?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Fidget-Spinner Fidget-Spinner Oct 13, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

We used to arbitrarily always consume 10 PyObject/PyStackRef per call. Meaning even if you had a single item, the whole 10 slot space would be used.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/bytecodes.c Outdated
inst(LOAD_BUILD_CLASS, ( -- bc)) {
PyObject *bc_o;
int err = PyMapping_GetOptionalItem(BUILTINS(), &_Py_ID(__build_class__), &bc_o);
PyObject *Py_MSVC_RESTRICT bc_o;

@zooba zooba Oct 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I wonder if there's a more semantically meaningful name than Py_MSVC_RESTRICT here? "Unaliased" or "no escape"?

And possibly define it as a macro like #define UNALIASED(x) x Py_MSVC_RESTRICT to get UNALIASED(PyObject *) bc_o?

Just looking to reduce the sense of "oh I have to go look up this MSVC thing to know what's going on here" when reading the code. There may be other ways.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Fidget-Spinner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Python/bytecodes.c Outdated
Comment on lines +2294 to +2296
int *Py_MSVC_RESTRICT method_found_ptr = &method_found;
attr_o = _PySuper_Lookup(cls, self, name,
Py_TYPE(self)->tp_getattro == PyObject_GenericGetAttr ? method_found_ptr : NULL);

@zooba zooba Oct 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Indent? Like in other places

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Python/bytecodes.c Outdated
PyObject *stack[] = {class, self};
PyObject *super = PyObject_Vectorcall(global_super, stack, oparg & 2, NULL);
PyObject *super;
Py_BEGIN_LOCALS_MUST_NOT_ESCAPE();

@zooba zooba Oct 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

We usually don't require parentheses on macros like this, especially when they define a scope.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread PCbuild/pythoncore.vcxproj
<ClCompile Include="..\Python\brc.c" />
<ClCompile Include="..\Python\ceval.c" />
<ClCompile Include="..\Python\ceval.c">
<AdditionalOptions Condition="'$(UseTailCallInterp)' == 'true' and $(PlatformToolset) != 'ClangCL'">/std:clatest %(AdditionalOptions)</AdditionalOptions>

@zooba zooba Oct 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

What happens if we pass this option to clang-cl? Does it break?

Any possibility of passing a specific /std:c rather than "latest"?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@Fidget-Spinner Fidget-Spinner Oct 13, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

So I've worked with Chris on this and apparently it builds with clang-cl still the last time I checked with him? @chris-eibl

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chris-eibl reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@chris-eibl chris-eibl Oct 13, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

What happens if we pass this option to clang-cl? Does it break?

Yupp, clang-cl doesn't like /std:clatest, hence I omitted it, because it doesn't need it.
Seems the clang-cl versions we are using (>=19)

  • support by default at least C99 (which is would be needed for restrict)
  • and support must tail, etc, in the "clang specific notation"

But I assume MSVC needs /std:clatest for [[msvc::musttail]] - @Fidget-Spinner correct?
And at least MSVC definitely needs at least /std:c11 for restrict (https://learn.microsoft.com/en-us/cpp/build/reference/std-specify-language-standard-version?view=msvc-170).

Any possibility of passing a specific /std:c rather than "latest"?

Yeah, we could explicitely enforce /std:c11for clang-cl, but we didn't need it in the past and AFAICT this PR didn't change anything that clang-cl sees, due to carefully crafted combinations of _MSC_VER and __clang__ like

#if defined(_MSC_VER) && !defined(__clang__)

and apparently it builds with clang-cl still the last time I checked with him?

Yes, it still does. Having done a lot of builds recently and still playing around.
And tail calling CI is green as well: https://github.com/python/cpython/actions/runs/18435075900/job/52527447202

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

zooba reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@Fidget-Spinner Fidget-Spinner Oct 13, 2025

Copy link
Copy Markdown
Member Author

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I'm under the impression we need clatest for msvc musttail as well.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions

@chris-eibl chris-eibl Oct 19, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

I've just verified again:

  • MSVC needs /std:clatest for [[msvc::musttail]]
  • using /std:c17 fails with error C2143: syntax error: missing ';' before '['
  • clang-cl doesn't accept /std:clatest

So atm we're stuck with this construct until MSVC introduces a new "fixed" option (or maybe accepts [[msvc::musttail]] in /std:c17).

I agree with Steve #135927 (comment)

For the record, I don't like building with Clatest and if we're considering making that change upstream, we should pick a version.

and we should revisit this once we know a fixed version option for it.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Fidget-Spinner and others added 3 commits October 14, 2025 04:49
@Fidget-Spinner @chris-eibl
Reduce number of restricts
e699d40
Co-Authored-By: Chris Eibl <[email protected]>
@Fidget-Spinner
Merge branch 'msvc-tailcall-take-two' of https://github.com/Fidget-Sp…
66d6c39
…inner/cpython into msvc-tailcall-take-two
@Fidget-Spinner
Reduce restrict use even more, reduce usage
5584fec
zooba
zooba reviewed Oct 13, 2025
View reviewed changes
Comment thread Include/internal/pycore_ceval.h Outdated
# define Py_NO_INLINE_MSVC_TAILCALL
#endif

// Tells the compiler that this variable cannot be alised.

@zooba zooba Oct 13, 2025

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Grammar nit, but "cannot be aliased" is ambiguous (because it's not clear whether we are choosing not to alias it, or telling the compiler that it cannot choose to alias it).

I think we're promising that it won't be aliased? So "Tells the compiler to trust that we haven't aliased this variable" better explains what it's doing. Or just "... this variable is not going to be aliased".

Alternatively, "Tells the compiler not to alias the variable" (but I don't think that even makes sense, let alone being correct here).

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@markshannon

markshannon commented Oct 14, 2025

Copy link
Copy Markdown
Member

What is necessary for tail calling?

IIUC, the restict annotations and the tighter scoping around arrays in the interpreter are necessary, but moving the temporary arrays from the C stack to the threadstate are not. Is that correct?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Fidget-Spinner

Fidget-Spinner commented Oct 14, 2025

Copy link
Copy Markdown
Member Author

IIUC, the restict annotations and the tighter scoping around arrays in the interpreter are necessary, but moving the temporary arrays from the C stack to the threadstate are not. Is that correct?

No this is needed too. The problem is that the temporary arrays from the C stack are local variables. These variables are passed around in ways that are unpredictable to MSVC (such as passing to an arbitrary vectorcall function pointer). So we have to move this to thread state to get tail calling.

There are other side benefits and I've wanted to do this for awhile, so I thought might as well put this in this PR. Do you still want to split up the restrict stuff knowing that we need both to get TC on MSVC?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@markshannon

markshannon commented Oct 15, 2025

Copy link
Copy Markdown
Member

This PR should contain everything that is necessary for tailcalling. If that includes moving the temporary arrays then move them.

But, why is it necessary to move the arrays? If this array https://github.com/python/cpython/pull/139962/files#diff-729a985b0cb8b431cb291f1edb561bbbfea22e3f8c262451cd83328a0936a342R3522 is made OK by adding parentheses, then why not the temporary arrays? In fact, why do we need to insert the parentheses in bytecodes.c at all. Why not do it in the code generator as we discussed a while ago?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Fidget-Spinner

Fidget-Spinner commented Oct 15, 2025 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

I'm not sure what MSVC is doing under the hood here, but the last time I tried, just adding scoping around the temporary arrays doesn't work.

It seems to be more lines of code and more maintainence burden to add functionality to the cases generator to detect what is the smallest possible scope to wrap a escaping local variable around. We dont have that many cases in bytecodes.c so I think we should just do it manually. Note that we cant just add curly braces, we need to pass variables around too in certain cases.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Fidget-Spinner Fidget-Spinner mentioned this pull request Oct 18, 2025
gh-139109: A new tracing JIT compiler frontend for CPython #140310
Merged
@Fidget-Spinner

Fidget-Spinner commented Oct 22, 2025

Copy link
Copy Markdown
Member Author

@markshannon gentle ping to see the latest comment please, thanks!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@Fidget-Spinner @chris-eibl
Apply Chris' changes
7eeeaa8
Co-Authored-By: Chris Eibl <[email protected]>
@chris-eibl chris-eibl mentioned this pull request Oct 24, 2025
We should fail to compile if _Py_TAIL_CALL_INTERP is set but preserve_none and musttail do not exist. #140513
Closed
Fidget-Spinner and others added 3 commits November 4, 2025 20:54
@Fidget-Spinner
Merge remote-tracking branch 'upstream/main' into msvc-tailcall-take-two
6f3d525
@Fidget-Spinner
Merge remote-tracking branch 'upstream/main' into msvc-tailcall-take-two
81618e2
@Fidget-Spinner
Use choco for now to get VS 2026
2008d1d
@Fidget-Spinner
Fidget-Spinner requested review from AA-Turner, ezio-melotti and hugovk as code owners November 11, 2025 22:49
Fidget-Spinner added 7 commits November 11, 2025 22:49
@Fidget-Spinner
Update tail-call.yml
7c84388
@Fidget-Spinner
Update tail-call.yml
68b41cf
@Fidget-Spinner
Update tail-call.yml
c7316fc
@Fidget-Spinner
Update tail-call.yml
9214d5b
@Fidget-Spinner
Update tail-call.yml
52c6f9c
@Fidget-Spinner
Update tail-call.yml
34d98d3
@Fidget-Spinner
Update tail-call.yml
7ec626e
@Fidget-Spinner

Fidget-Spinner commented Nov 11, 2025

Copy link
Copy Markdown
Member Author

Sorry for the noise. Trying to get CI to download VS 2026 programatically and compile.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Fidget-Spinner added 2 commits November 11, 2025 23:48
@Fidget-Spinner
Update tail-call.yml
ad1c5a2
@Fidget-Spinner
Update tail-call.yml
4155337
@Fidget-Spinner

Fidget-Spinner commented Nov 11, 2025

Copy link
Copy Markdown
Member Author

CI wranging is too difficult. I wail wait till GHA updates itself then use that. Sorry for the noise everyone!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Fidget-Spinner added a commit that referenced this pull request Nov 13, 2025
@Fidget-Spinner
gh-139109: A new tracing JIT compiler frontend for CPython (GH-140310)
4fa80ce
This PR changes the current JIT model from trace projection to trace recording. Benchmarking: better pyperformance (about 1.7% overall) geomean versus current https://raw.githubusercontent.com/facebookexperimental/free-threading-benchmarking/refs/heads/main/results/bm-20251108-3.15.0a1%2B-7e2bc1d-JIT/bm-20251108-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-7e2bc1d-vs-base.svg, 100% faster Richards on the most improved benchmark versus the current JIT. Slowdown of about 10-15% on the worst benchmark versus the current JIT. **Note: the fastest version isn't the one merged, as it relies on fixing bugs in the specializing interpreter, which is left to another PR**. The speedup in the merged version is about 1.1%. https://raw.githubusercontent.com/facebookexperimental/free-threading-benchmarking/refs/heads/main/results/bm-20251112-3.15.0a1%2B-f8a764a-JIT/bm-20251112-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-f8a764a-vs-base.svg

Stats: 50% more uops executed, 30% more traces entered the last time we ran them. It also suggests our trace lengths for a real trace recording JIT are too short, as a lot of trace too long aborts https://github.com/facebookexperimental/free-threading-benchmarking/blob/main/results/bm-20251023-3.15.0a1%2B-eb73378-CLANG%2CJIT/bm-20251023-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-eb73378-pystats-vs-base.md .

This new JIT frontend is already able to record/execute significantly more instructions than the previous JIT frontend. In this PR, we are now able to record through custom dunders, simple object creation, generators, etc. None of these were done by the old JIT frontend. Some custom dunders uops were discovered to be broken as part of this work gh-140277

The optimizer stack space check is disabled, as it's no longer valid to deal with underflow.

Pros:
* Ignoring the generated tracer code as it's automatically created, this is only additional 1k lines of code. The maintenance burden is handled by the DSL and code generator.
* `optimizer.c` is now significantly simpler, as we don't have to do strange things to recover the bytecode from a trace.
* The new JIT frontend is able to handle a lot more control-flow than the old one.
* Tracing is very low overhead. We use the tail calling interpreter/computed goto interpreter to switch between tracing mode and non-tracing mode. I call this mechanism dual dispatch, as we have two dispatch tables dispatching to each other. Specialization is still enabled while tracing.
* Better handling of polymorphism. We leverage the specializing interpreter for this.

Cons:
* (For now) requires tail calling interpreter or computed gotos. This means no Windows JIT for now :(. Not to fret, tail calling is coming soon to Windows though #139962

Design:
* After each instruction, the `record_previous_inst` function/label is executed. This does as the name suggests.
* The tracing interpreter lowers bytecode to uops directly so that it can obtain "fresh" values at the point of lowering.
* The tracing version behaves nearly identical to the normal interpreter, in fact it even has specialization! This allows it to run without much of a slowdown when tracing. The actual cost of tracing is only a function call and writes to memory.
* The tracing interpreter uses the specializing interpreter's deopt to naturally form the side exit chains. This allows it to side exit chain effectively, without repeating much code. We force a re-specializing when tracing a deopt.
* The tracing interpreter can even handle goto errors/exceptions, but I chose to disable them for now as it's not tested.
* Because we do not share interpreter dispatch, there is should be no significant slowdown to the original specializing interpreter on tailcall and computed got with JIT disabled. With JIT enabled, there might be a slowdown in the form of the JIT trying to trace.
* Things that could have dynamic instruction pointer effects are guarded on. The guard deopts to a new instruction --- `_DYNAMIC_EXIT`.
StanFromIreland pushed a commit to StanFromIreland/cpython that referenced this pull request Dec 6, 2025
@Fidget-Spinner @StanFromIreland
pythongh-139109: A new tracing JIT compiler frontend for CPython (pyt…
6ddc353
…honGH-140310)

This PR changes the current JIT model from trace projection to trace recording. Benchmarking: better pyperformance (about 1.7% overall) geomean versus current https://raw.githubusercontent.com/facebookexperimental/free-threading-benchmarking/refs/heads/main/results/bm-20251108-3.15.0a1%2B-7e2bc1d-JIT/bm-20251108-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-7e2bc1d-vs-base.svg, 100% faster Richards on the most improved benchmark versus the current JIT. Slowdown of about 10-15% on the worst benchmark versus the current JIT. **Note: the fastest version isn't the one merged, as it relies on fixing bugs in the specializing interpreter, which is left to another PR**. The speedup in the merged version is about 1.1%. https://raw.githubusercontent.com/facebookexperimental/free-threading-benchmarking/refs/heads/main/results/bm-20251112-3.15.0a1%2B-f8a764a-JIT/bm-20251112-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-f8a764a-vs-base.svg

Stats: 50% more uops executed, 30% more traces entered the last time we ran them. It also suggests our trace lengths for a real trace recording JIT are too short, as a lot of trace too long aborts https://github.com/facebookexperimental/free-threading-benchmarking/blob/main/results/bm-20251023-3.15.0a1%2B-eb73378-CLANG%2CJIT/bm-20251023-vultr-x86_64-Fidget%252dSpinner-tracing_jit-3.15.0a1%2B-eb73378-pystats-vs-base.md .

This new JIT frontend is already able to record/execute significantly more instructions than the previous JIT frontend. In this PR, we are now able to record through custom dunders, simple object creation, generators, etc. None of these were done by the old JIT frontend. Some custom dunders uops were discovered to be broken as part of this work pythongh-140277

The optimizer stack space check is disabled, as it's no longer valid to deal with underflow.

Pros:
* Ignoring the generated tracer code as it's automatically created, this is only additional 1k lines of code. The maintenance burden is handled by the DSL and code generator.
* `optimizer.c` is now significantly simpler, as we don't have to do strange things to recover the bytecode from a trace.
* The new JIT frontend is able to handle a lot more control-flow than the old one.
* Tracing is very low overhead. We use the tail calling interpreter/computed goto interpreter to switch between tracing mode and non-tracing mode. I call this mechanism dual dispatch, as we have two dispatch tables dispatching to each other. Specialization is still enabled while tracing.
* Better handling of polymorphism. We leverage the specializing interpreter for this.

Cons:
* (For now) requires tail calling interpreter or computed gotos. This means no Windows JIT for now :(. Not to fret, tail calling is coming soon to Windows though python#139962

Design:
* After each instruction, the `record_previous_inst` function/label is executed. This does as the name suggests.
* The tracing interpreter lowers bytecode to uops directly so that it can obtain "fresh" values at the point of lowering.
* The tracing version behaves nearly identical to the normal interpreter, in fact it even has specialization! This allows it to run without much of a slowdown when tracing. The actual cost of tracing is only a function call and writes to memory.
* The tracing interpreter uses the specializing interpreter's deopt to naturally form the side exit chains. This allows it to side exit chain effectively, without repeating much code. We force a re-specializing when tracing a deopt.
* The tracing interpreter can even handle goto errors/exceptions, but I chose to disable them for now as it's not tested.
* Because we do not share interpreter dispatch, there is should be no significant slowdown to the original specializing interpreter on tailcall and computed got with JIT disabled. With JIT enabled, there might be a slowdown in the form of the JIT trying to trace.
* Things that could have dynamic instruction pointer effects are guarded on. The guard deopts to a new instruction --- `_DYNAMIC_EXIT`.
@chris-eibl chris-eibl mentioned this pull request Dec 11, 2025
GH-141362: Make get_externals handle fetching platform-specific release artifacts #142405
Merged
@chris-eibl chris-eibl mentioned this pull request Dec 22, 2025
GH-139922: Tail calling for MSVC (VS 2026) #143068
Merged
@Fidget-Spinner Fidget-Spinner closed this Dec 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@zooba zooba zooba left review comments
@markshannon markshannon markshannon left review comments
@ZeroIntensity ZeroIntensity ZeroIntensity left review comments
@chris-eibl chris-eibl chris-eibl left review comments
@ericsnowcurrently ericsnowcurrently Awaiting requested review from ericsnowcurrently ericsnowcurrently is a code owner
@methane methane Awaiting requested review from methane
@ezio-melotti ezio-melotti Awaiting requested review from ezio-melotti ezio-melotti is a code owner
@hugovk hugovk Awaiting requested review from hugovk hugovk is a code owner
@AA-Turner AA-Turner Awaiting requested review from AA-Turner AA-Turner is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

awaiting core review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@Fidget-Spinner @markshannon @zooba @ZeroIntensity @chris-eibl
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.