https://github.com/python/cpython/commit/f93834ff01128774532c101c574e47c6c0418540
commit: f93834ff01128774532c101c574e47c6c0418540
branch: main
author: Mark Shannon <[email protected]>
committer: markshannon <[email protected]>
date: 2026-04-22T11:09:05+01:00
summary:

GH-146073: Add example script for dumping JIT traces (GH-148840)

files:
A Tools/jit/example_trace_dump.py
M Tools/jit/README.md

diff --git a/Tools/jit/README.md b/Tools/jit/README.md
index fd7154d0e76d0a..9361f39dcc64f2 100644
--- a/Tools/jit/README.md
+++ b/Tools/jit/README.md
@@ -86,3 +86,8 @@ If you're looking for information on how to update the JIT 
build dependencies, s
 [^pep-744]: [PEP 744](https://peps.python.org/pep-0744/)
 
 [^why-llvm]: Clang is specifically needed because it's the only C compiler 
with support for guaranteed tail calls (`musttail`), which are required by 
CPython's continuation-passing-style approach to JIT compilation. Since LLVM 
also includes other functionalities we need (namely, object file parsing and 
disassembly), it's convenient to only support one toolchain at this time.
+
+### Understanding JIT behavior
+
+The [example_trace_dump.py](./example_trace_dump.py) script will (when 
configured as described in the script) dump out the
+executors for a range of tiny programs to show the behavior of the JIT 
front-end.
\ No newline at end of file
diff --git a/Tools/jit/example_trace_dump.py b/Tools/jit/example_trace_dump.py
new file mode 100644
index 00000000000000..e3c3df94059044
--- /dev/null
+++ b/Tools/jit/example_trace_dump.py
@@ -0,0 +1,191 @@
+# This script is best run with pystats enabled to help visualize the shape of 
the traces.
+# ./configure --enable-experimental-jit=interpreter  -C --with-pydebug 
--enable-pystats
+
+# The resulting images can be visualize on linux as follows:
+# $ cd folder_with_gv_files
+# $ dot -Tsvg -Osvg *.gv
+# $ firefox *.gv.svg
+
+# type: ignore
+
+import sys
+import os.path
+from types import FunctionType
+
+# All functions declared in this module will be run to generate
+# a .gv file of the executors, unless the name starts with an underscore.
+
+
+def _gen(n):
+    for _ in range(n):
+        yield n
+
+
+def gen_in_loop(n):
+    t = 0
+    for n in _gen(n):
+        t += n
+    return n
+
+
+def short_loop(n):
+    t = 0
+    for _ in range(n):
+        t += 1
+        t += 1
+        t += 1
+        t += 1
+        t += 1
+    return t
+
+
+exec(
+    "\n".join(
+        ["def mid_loop(n):"]
+        + ["    t = 0"]
+        + ["    for _ in range(n):"]
+        + ["        t += 1"] * 20
+        + ["    return t"]
+    ),
+    globals(),
+)
+
+exec(
+    "\n".join(
+        ["def long_loop(n):"]
+        + ["    t = 0"]
+        + ["    for _ in range(n):"]
+        + ["        t += 1"] * 100
+        + ["    return t"]
+    ),
+    globals(),
+)
+
+
+def _add(a, b):
+    return a + b
+
+
+def short_loop_with_calls(n):
+    t = 0
+    for _ in range(n):
+        t = _add(t, 1)
+        t = _add(t, 1)
+        t = _add(t, 1)
+        t = _add(t, 1)
+        t = _add(t, 1)
+    return t
+
+
+exec(
+    "\n".join(
+        ["def mid_loop_with_calls(n):"]
+        + ["    t = 0"]
+        + ["    for _ in range(n):"]
+        + ["        t = _add(t, 1)"] * 20
+        + ["    return t"]
+    ),
+    globals(),
+)
+
+exec(
+    "\n".join(
+        ["def long_loop_with_calls(n):"]
+        + ["    t = 0"]
+        + ["    for _ in range(n):"]
+        + ["        t = _add(t, 1)"] * 100
+        + ["    return t"]
+    ),
+    globals(),
+)
+
+
+def short_loop_with_side_exits(n):
+    t = 0
+    for i in range(n):
+        if t < 0:
+            break
+        t += 1
+        if t < 0:
+            break
+        t += 1
+        if t < 0:
+            break
+        t += 1
+        if t < 0:
+            break
+        t += 1
+        if t < 0:
+            break
+        t += 1
+    return t
+
+
+exec(
+    "\n".join(
+        ["def mid_loop_with_side_exits(n):"]
+        + ["    t = 0"]
+        + ["    for _ in range(n):"]
+        + ["        if t < 0:", "            break", "        t += 1"] * 20
+        + ["    return t"]
+    ),
+    globals(),
+)
+
+exec(
+    "\n".join(
+        ["def long_loop_with_side_exits(n):"]
+        + ["    t = 0"]
+        + ["    for _ in range(n):"]
+        + ["        if t < 0:", "            break", "        t += 1"] * 100
+        + ["    return t"]
+    ),
+    globals(),
+)
+
+
+def short_branchy_loop(n):
+    # Branches are correlated and exit 1 time in 4.
+    t = 0
+    for i in range(n):
+        # Start with a few operations to form a viable trace
+        t += 1
+        t += 1
+        t += 1
+        if not t & 6:
+            continue
+        t += 1
+        if not t & 12:
+            continue
+        t += 1
+        if not t & 24:
+            continue
+        t += 1
+        if not t & 48:
+            continue
+        t += 1
+    return t
+
+
+def _run_and_dump(func, n, outdir):
+    sys._clear_internal_caches()
+    func(n)
+    sys._dump_tracelets(os.path.join(outdir, f"{func.__name__}.gv"))
+
+
+def _main():
+    if len(sys.argv) < 2 or len(sys.argv) > 3:
+        print(f"Usage: {sys.argv[0] if sys.argv else " "} OUTDIR [loops]")
+    outdir = sys.argv[1]
+    n = int(sys.argv[2]) if len(sys.argv) > 2 else 5000
+    functions = [
+        func
+        for func in globals().values()
+        if isinstance(func, FunctionType) and not func.__name__.startswith("_")
+    ]
+    for func in functions:
+        _run_and_dump(func, n, outdir)
+
+
+if __name__ == "__main__":
+    _main()

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to