Skip to content

gh-154124: Reserve frame pointer on Clang FP+TC builds - #159204

Open
Fidget-Spinner wants to merge 15 commits into
python:mainfrom
Fidget-Spinner:meowl
Open

Fidget-Spinner wants to merge 15 commits into
python:mainfrom
Fidget-Spinner:meowl

Conversation

@Fidget-Spinner

@Fidget-Spinner Fidget-Spinner commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

So both TC and Computed goto interpreter take a 2% hit when enabling frame pointers.

However, on TC interpreter, if we just let the first handler (start_frame) push a frame pointer, then reserve it throughout the opcode-opcode tail calls, we remove all overhead of the prologue/epilogue on each tail call handler except for the first one entering the interpreter.

I measured on my machine, and it's a 2% speedup on x86-64 for fastmark. In other words, this makes frame pointer builds zero-overhead for tail calling interpreter!

To do this without horrible pragma hacks, we need to move the frame-pointer reserved opcode handlers to a separate compilation unit and pass in separate flags for that compilation unit. The rest of the CPython still builds with default frame pointer flags.

@Fidget-Spinner
Fidget-Spinner requested a review from a team as a code owner October 11, 2026 22:18
@pablogsal
pablogsal self-requested a review October 12, 2026 00:07

@pablogsal pablogsal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

a first pass late at night :)


def function_proto(name: str) -> str:
return f"static PyObject *Py_PRESERVE_NONE_CC _TAIL_CALL_{name}(TAIL_CALL_PARAMS)"
def function_signature(name: str) -> str:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes every handler and both tables external symbols, and neither _TAIL_CALL_* nor instruction_funcptr_handler_table is in our namespace. Unless I am missing something, make smelly will fail on a --with-tail-call-interp build because these show up as global symbols in libpython3.X.a (CI only runs it on a non tail-call build so we don't see it).

_testinternalcapi/interpreter.c now also defines the same names, so we are relying on -fvisibility=hidden to not interpose them, and a static build of that module would fail to link.

Can we give these a _Py prefix, and use a different one for the test interpreter?

Comment thread Python/ceval_tailcall.c
@@ -0,0 +1,13 @@
#include "pyconfig.h" // _Py_TAIL_CALL_INTERP

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should add this file to the paths filter in .github/workflows/tail-call.yml, otherwise changes here will not trigger the tail-call jobs. Probably Python/opcode_targets.h as well now that the tables are defined from it.

@@ -0,0 +1 @@
Reserve frame pointer on tail calling builds using newer Clang. This recovers most of the performance hit from frame pointers on tail calling builds using recent versions of Clang

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: can we say which Clang version is needed instead of "newer"/"recent", and reference :option:--with-tail-call-interp? This also says "tail calling builds using ... Clang" twice and is missing the final period.



def function_proto(name: str) -> str:
return f"extern {function_signature(name)}"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MODULE_BUILDTYPE=static now links duplicate TAIL_CALL* functions and dispatch tables from the core and _testinternalcapi. Can we keep the test interpreter's symbols static?

Comment thread Makefile.pre.in
Python/bltinmodule.o \
Python/brc.o \
Python/ceval.o \
Python/ceval_tailcall.o \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We also need ceval_tailcall.o in DTRACE_DEPS. Its probe-enabled checks require dtrace -G processing on FreeBSD, otherwise tail-call builds with DTrace fail to link.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants