https://github.com/python/cpython/commit/96c59a6e427fab32d0bca89b77febca8cba8aada
commit: 96c59a6e427fab32d0bca89b77febca8cba8aada
branch: main
author: danigm <[email protected]>
committer: savannahostrowski <[email protected]>
date: 2025-10-07T07:54:31-07:00
summary:

gh-138497: Support LLVM_VERSION configuration via env (#138498)

Co-authored-by: Savannah Ostrowski <[email protected]>

files:
A Misc/NEWS.d/next/Build/2025-09-04-12-16-31.gh-issue-138497.Y_5YXh.rst
M Tools/jit/README.md
M Tools/jit/_llvm.py
M Tools/jit/_targets.py
M Tools/jit/build.py
M configure
M configure.ac

diff --git 
a/Misc/NEWS.d/next/Build/2025-09-04-12-16-31.gh-issue-138497.Y_5YXh.rst 
b/Misc/NEWS.d/next/Build/2025-09-04-12-16-31.gh-issue-138497.Y_5YXh.rst
new file mode 100644
index 00000000000000..7eb0770996877a
--- /dev/null
+++ b/Misc/NEWS.d/next/Build/2025-09-04-12-16-31.gh-issue-138497.Y_5YXh.rst
@@ -0,0 +1,4 @@
+The LLVM version used by the JIT at build time can now be modified using
+the ``LLVM_VERSION`` environment variable. Use this at your own risk, as
+there is only one officially supported LLVM version. For more information,
+please check ``Tools/jit/README.md``.
diff --git a/Tools/jit/README.md b/Tools/jit/README.md
index ffc762d3828bfb..35c7ffd7a283f8 100644
--- a/Tools/jit/README.md
+++ b/Tools/jit/README.md
@@ -9,7 +9,7 @@ Python 3.11 or newer is required to build the JIT.
 
 The JIT compiler does not require end users to install any third-party 
dependencies, but part of it must be *built* using LLVM[^why-llvm]. You are 
*not* required to build the rest of CPython using LLVM, or even the same 
version of LLVM (in fact, this is uncommon).
 
-LLVM version 19 is required. Both `clang` and `llvm-readobj` need to be 
installed and discoverable (version suffixes, like `clang-19`, are okay). It's 
highly recommended that you also have `llvm-objdump` available, since this 
allows the build script to dump human-readable assembly for the generated code.
+LLVM version 19 is the officially supported version. You can modify if needed 
using the `LLVM_VERSION` env var during configure. Both `clang` and 
`llvm-readobj` need to be installed and discoverable (version suffixes, like 
`clang-19`, are okay). It's highly recommended that you also have 
`llvm-objdump` available, since this allows the build script to dump 
human-readable assembly for the generated code.
 
 It's easy to install all of the required tools:
 
diff --git a/Tools/jit/_llvm.py b/Tools/jit/_llvm.py
index f09a8404871b24..bc3b50ffe61634 100644
--- a/Tools/jit/_llvm.py
+++ b/Tools/jit/_llvm.py
@@ -10,8 +10,8 @@
 
 import _targets
 
-_LLVM_VERSION = 19
-_LLVM_VERSION_PATTERN = 
re.compile(rf"version\s+{_LLVM_VERSION}\.\d+\.\d+\S*\s+")
+
+_LLVM_VERSION = "19"
 _EXTERNALS_LLVM_TAG = "llvm-19.1.7.0"
 
 _P = typing.ParamSpec("_P")
@@ -56,53 +56,66 @@ async def _run(tool: str, args: typing.Iterable[str], echo: 
bool = False) -> str
 
 
 @_async_cache
-async def _check_tool_version(name: str, *, echo: bool = False) -> bool:
+async def _check_tool_version(
+    name: str, llvm_version: str, *, echo: bool = False
+) -> bool:
     output = await _run(name, ["--version"], echo=echo)
-    return bool(output and _LLVM_VERSION_PATTERN.search(output))
+    _llvm_version_pattern = 
re.compile(rf"version\s+{llvm_version}\.\d+\.\d+\S*\s+")
+    return bool(output and _llvm_version_pattern.search(output))
 
 
 @_async_cache
-async def _get_brew_llvm_prefix(*, echo: bool = False) -> str | None:
-    output = await _run("brew", ["--prefix", f"llvm@{_LLVM_VERSION}"], 
echo=echo)
+async def _get_brew_llvm_prefix(llvm_version: str, *, echo: bool = False) -> 
str | None:
+    output = await _run("brew", ["--prefix", f"llvm@{llvm_version}"], 
echo=echo)
     return output and output.removesuffix("\n")
 
 
 @_async_cache
-async def _find_tool(tool: str, *, echo: bool = False) -> str | None:
+async def _find_tool(tool: str, llvm_version: str, *, echo: bool = False) -> 
str | None:
     # Unversioned executables:
     path = tool
-    if await _check_tool_version(path, echo=echo):
+    if await _check_tool_version(path, llvm_version, echo=echo):
         return path
     # Versioned executables:
-    path = f"{tool}-{_LLVM_VERSION}"
-    if await _check_tool_version(path, echo=echo):
+    path = f"{tool}-{llvm_version}"
+    if await _check_tool_version(path, llvm_version, echo=echo):
         return path
     # PCbuild externals:
     externals = os.environ.get("EXTERNALS_DIR", _targets.EXTERNALS)
     path = os.path.join(externals, _EXTERNALS_LLVM_TAG, "bin", tool)
-    if await _check_tool_version(path, echo=echo):
+    if await _check_tool_version(path, llvm_version, echo=echo):
         return path
     # Homebrew-installed executables:
-    prefix = await _get_brew_llvm_prefix(echo=echo)
+    prefix = await _get_brew_llvm_prefix(llvm_version, echo=echo)
     if prefix is not None:
         path = os.path.join(prefix, "bin", tool)
-        if await _check_tool_version(path, echo=echo):
+        if await _check_tool_version(path, llvm_version, echo=echo):
             return path
     # Nothing found:
     return None
 
 
 async def maybe_run(
-    tool: str, args: typing.Iterable[str], echo: bool = False
+    tool: str,
+    args: typing.Iterable[str],
+    echo: bool = False,
+    llvm_version: str = _LLVM_VERSION,
 ) -> str | None:
     """Run an LLVM tool if it can be found. Otherwise, return None."""
-    path = await _find_tool(tool, echo=echo)
+
+    path = await _find_tool(tool, llvm_version, echo=echo)
     return path and await _run(path, args, echo=echo)
 
 
-async def run(tool: str, args: typing.Iterable[str], echo: bool = False) -> 
str:
+async def run(
+    tool: str,
+    args: typing.Iterable[str],
+    echo: bool = False,
+    llvm_version: str = _LLVM_VERSION,
+) -> str:
     """Run an LLVM tool if it can be found. Otherwise, raise RuntimeError."""
-    output = await maybe_run(tool, args, echo=echo)
+
+    output = await maybe_run(tool, args, echo=echo, llvm_version=llvm_version)
     if output is None:
-        raise RuntimeError(f"Can't find {tool}-{_LLVM_VERSION}!")
+        raise RuntimeError(f"Can't find {tool}-{llvm_version}!")
     return output
diff --git a/Tools/jit/_targets.py b/Tools/jit/_targets.py
index 2f3969e7d0540c..9fc3522d23d982 100644
--- a/Tools/jit/_targets.py
+++ b/Tools/jit/_targets.py
@@ -50,6 +50,7 @@ class _Target(typing.Generic[_S, _R]):
     debug: bool = False
     verbose: bool = False
     cflags: str = ""
+    llvm_version: str = _llvm._LLVM_VERSION
     known_symbols: dict[str, int] = dataclasses.field(default_factory=dict)
     pyconfig_dir: pathlib.Path = pathlib.Path.cwd().resolve()
 
@@ -81,7 +82,9 @@ def _compute_digest(self) -> str:
     async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup:
         group = _stencils.StencilGroup()
         args = ["--disassemble", "--reloc", f"{path}"]
-        output = await _llvm.maybe_run("llvm-objdump", args, echo=self.verbose)
+        output = await _llvm.maybe_run(
+            "llvm-objdump", args, echo=self.verbose, 
llvm_version=self.llvm_version
+        )
         if output is not None:
             # Make sure that full paths don't leak out (for reproducibility):
             long, short = str(path), str(path.name)
@@ -99,7 +102,9 @@ async def _parse(self, path: pathlib.Path) -> 
_stencils.StencilGroup:
             "--sections",
             f"{path}",
         ]
-        output = await _llvm.run("llvm-readobj", args, echo=self.verbose)
+        output = await _llvm.run(
+            "llvm-readobj", args, echo=self.verbose, 
llvm_version=self.llvm_version
+        )
         # --elf-output-style=JSON is only *slightly* broken on Mach-O...
         output = output.replace("PrivateExtern\n", "\n")
         output = output.replace("Extern\n", "\n")
@@ -175,12 +180,16 @@ async def _compile(
             # Allow user-provided CFLAGS to override any defaults
             *shlex.split(self.cflags),
         ]
-        await _llvm.run("clang", args_s, echo=self.verbose)
+        await _llvm.run(
+            "clang", args_s, echo=self.verbose, llvm_version=self.llvm_version
+        )
         self.optimizer(
             s, label_prefix=self.label_prefix, symbol_prefix=self.symbol_prefix
         ).run()
         args_o = [f"--target={self.triple}", "-c", "-o", f"{o}", f"{s}"]
-        await _llvm.run("clang", args_o, echo=self.verbose)
+        await _llvm.run(
+            "clang", args_o, echo=self.verbose, llvm_version=self.llvm_version
+        )
         return await self._parse(o)
 
     async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
@@ -224,6 +233,8 @@ def build(
         if not self.stable:
             warning = f"JIT support for {self.triple} is still experimental!"
             request = "Please report any issues you 
encounter.".center(len(warning))
+            if self.llvm_version != _llvm._LLVM_VERSION:
+                request = f"Warning! Building with an LLVM version other than 
{_llvm._LLVM_VERSION} is not supported."
             outline = "=" * len(warning)
             print("\n".join(["", outline, warning, request, outline, ""]))
         digest = f"// {self._compute_digest()}\n"
diff --git a/Tools/jit/build.py b/Tools/jit/build.py
index a0733005929bf2..127d93b317fb09 100644
--- a/Tools/jit/build.py
+++ b/Tools/jit/build.py
@@ -42,6 +42,7 @@
     parser.add_argument(
         "--cflags", help="additional flags to pass to the compiler", default=""
     )
+    parser.add_argument("--llvm-version", help="LLVM version to use")
     args = parser.parse_args()
     for target in args.target:
         target.debug = args.debug
@@ -49,6 +50,8 @@
         target.verbose = args.verbose
         target.cflags = args.cflags
         target.pyconfig_dir = args.pyconfig_dir
+        if args.llvm_version:
+            target.llvm_version = args.llvm_version
         target.build(
             comment=comment,
             force=args.force,
diff --git a/configure b/configure
index 0d1f6a29e9b432..d80340e3015bee 100755
--- a/configure
+++ b/configure
@@ -10875,7 +10875,7 @@ then :
 
 else case e in #(
   e) as_fn_append CFLAGS_NODIST " $jit_flags"
-           REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) 
\$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . 
--pyconfig-dir . --cflags=\"$CFLAGS_JIT\""
+           REGEN_JIT_COMMAND="\$(PYTHON_FOR_REGEN) 
\$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . 
--pyconfig-dir . --cflags=\"$CFLAGS_JIT\" --llvm-version=\"$LLVM_VERSION\""
            if test "x$Py_DEBUG" = xtrue
 then :
   as_fn_append REGEN_JIT_COMMAND " --debug"
diff --git a/configure.ac b/configure.ac
index 7b5da6e0d15682..1e0c0f71b7c281 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2786,7 +2786,7 @@ AS_VAR_IF([jit_flags],
           [],
           [AS_VAR_APPEND([CFLAGS_NODIST], [" $jit_flags"])
            AS_VAR_SET([REGEN_JIT_COMMAND],
-                      ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py 
${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . 
--cflags=\"$CFLAGS_JIT\""])
+                      ["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py 
${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\" 
--llvm-version=\"$LLVM_VERSION\""])
            AS_VAR_IF([Py_DEBUG],
                      [true],
                      [AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])],

_______________________________________________
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