Skip to content

Conversation

@diegorusso
Copy link
Contributor

@diegorusso diegorusso commented Jan 22, 2026

Add a test that unwinds the stack using frame pointers. We test on any platform that frame pointers are enable (the others will be skipped). It tests with and without the JIT.

Output with PYTHON_JIT=0

#00 0xbcb53b0332f0 -> python
#01 0xbcb53b1c42b8 -> python
#02 0xbcb53b1ce4b8 -> python
#03 0xbcb53b1d758c -> python
#04 0xbcb53b0332f0 -> python
#05 0xbcb53b1c4774 -> python
#06 0xbcb53b1cfaec -> python
...
#68 0xbe2cbdec495c -> python
#69 0xeaad695b259c -> other
#70 0x60eaad695b267c -> other
#71 0x6be2cbdbc63f0 -> other
{"length": 53, "python_frames": 50, "jit_frames": 0, "other_frames": 3, "jit_code_ranges": 0, "jit_backend": "jit"}

Output with PYTHON_JIT=1

JIT ranges:
  0xeaad6985c000-0xeaad6985e000
#00 0xbe2cbdc332f0 -> python
#01 0xbe2cbddc42b8 -> python
#02 0xbe2cbddce4b8 -> python
#03 0xbe2cbddd758c -> python
#04 0xbe2cbdc332f0 -> python
#05 0xbe2cbddc4774 -> python
#06 0xeaad6985c898 -> jit
...
#61 0xeaad6985b050 -> jit
#62 0xbe2cbddd42a4 -> python
#63 0xbe2cbddd72d4 -> python
#64 0xbe2cbde90d3c -> python
#65 0xbe2cbde910d0 -> python
#66 0xbe2cbde93490 -> python
#67 0xbe2cbdec3d30 -> python
#68 0xbe2cbdec495c -> python
#69 0xeaad695b259c -> other
#70 0x60eaad695b267c -> other
#71 0x6be2cbdbc63f0 -> other
{"length": 72, "python_frames": 49, "jit_frames": 20, "other_frames": 3, "jit_code_ranges": 1, "jit_backend": "jit"}

The core of the test has been taken from @pablogsal repository https://github.com/pablogsal/cpython-unwind

@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @diegorusso for commit 5d53a15 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F144137%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jan 22, 2026
@diegorusso diegorusso added the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jan 22, 2026
@bedevere-bot
Copy link

🤖 New build scheduled with the buildbot fleet by @diegorusso for commit c179848 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F144137%2Fmerge

If you want to schedule another build, you need to add the 🔨 test-with-buildbots label again.

@bedevere-bot bedevere-bot removed the 🔨 test-with-buildbots Test PR w/ buildbots; report in status section label Jan 22, 2026
jit_enabled = hasattr(sys, "_jit") and sys._jit.is_enabled()
jit_backend = _testinternalcapi.get_jit_backend()
ranges = _testinternalcapi.get_jit_code_ranges() if jit_enabled else []
jit_code_ranges = len(ranges)
Copy link
Member

Choose a reason for hiding this comment

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

This is not used anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that's a leftover of a previous logic to test the existence of JIT frames. Will be removing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess you referred only to jit_code_ranges = len(ranges). The rest is needed.

Comment on lines +321 to +330
get_jit_backend(PyObject *self, PyObject *Py_UNUSED(args))
{
#ifdef _Py_JIT
return PyUnicode_FromString("jit");
#elif defined(_Py_TIER2)
return PyUnicode_FromString("interpreter");
#else
Py_RETURN_NONE;
#endif
}
Copy link
Member

Choose a reason for hiding this comment

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

There's https://docs.python.org/3/library/sys.html#sys._jit which you can use instead of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately that API doesn't differentiate between --enable-eperimental-jit and --enable-experimental-jit=interpreter. In both cases I have:

>>> sys._jit.is_enabled()
True
>>> sys._jit.is_available()
True

This is not useful because it doesn't tell me anything about the backend.

For testing purposes I need to know if the backend is just the interpreter because it means that I don't have any memory allocated for the JIT. This is really a corner case but in our CI we do build --enable-experimental-jit=interpreter as a gatekeeper for the JIT jobs.

return "python";
}
return "other";
}
Copy link
Member

Choose a reason for hiding this comment

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

if the module lookup succeeds but filename retrieval fails (len == 0 or len >= MAX_PATH), it falls through without returning anything, This would then check JIT, which is probably fine, but the logic is unclear. Perhaps we can add an explicit return "other"; inside the outer if block after the inner if.

}
#else
static const char *
classify_address(uintptr_t addr, int jit_enabled, PyInterpreterState *interp)
Copy link
Member

Choose a reason for hiding this comment

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

Do you get a warning here for interp being unused? I would have expected the compiler to complain for platforms without HAVE_DLADDR or WASI 🤔


uintptr_t *next_fp = (uintptr_t *)frame_pointer[0];
// Stop if the frame pointer is extremely low.
if ((uintptr_t)next_fp < 0x1000) {
Copy link
Member

Choose a reason for hiding this comment

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

Super nit (feel free to ignore): extract a global with a name for 0x1000

self.assertEqual(
python_frames,
1,
f"unexpected Python frames counted on {self.machine} with env {env}",
Copy link
Member

Choose a reason for hiding this comment

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

We can see how CI behaves here but when frame pointers aren't expected, asserting exactly 1 Python frame seems brittle. If the unwinder happens to traverse one valid frame before hitting invalid data, this works, but this behavior isn't guaranteed. Consider whether <= 1 perhaps?

Copy link
Member

@pablogsal pablogsal left a comment

Choose a reason for hiding this comment

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

LGTM! I just left a bunch of minor nits. Feel free to land without those

@pablogsal
Copy link
Member

Very cool and great work @diegorusso 💯

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.

4 participants