Skip to content

Commit 96c59a6

Browse files
gh-138497: Support LLVM_VERSION configuration via env (#138498)
Co-authored-by: Savannah Ostrowski <[email protected]>
1 parent 7094f09 commit 96c59a6

File tree

7 files changed

+56
-25
lines changed

7 files changed

+56
-25
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
The LLVM version used by the JIT at build time can now be modified using
2+
the ``LLVM_VERSION`` environment variable. Use this at your own risk, as
3+
there is only one officially supported LLVM version. For more information,
4+
please check ``Tools/jit/README.md``.

Tools/jit/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Python 3.11 or newer is required to build the JIT.
99

1010
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).
1111

12-
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.
12+
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.
1313

1414
It's easy to install all of the required tools:
1515

Tools/jit/_llvm.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
import _targets
1212

13-
_LLVM_VERSION = 19
14-
_LLVM_VERSION_PATTERN = re.compile(rf"version\s+{_LLVM_VERSION}\.\d+\.\d+\S*\s+")
13+
14+
_LLVM_VERSION = "19"
1515
_EXTERNALS_LLVM_TAG = "llvm-19.1.7.0"
1616

1717
_P = typing.ParamSpec("_P")
@@ -56,53 +56,66 @@ async def _run(tool: str, args: typing.Iterable[str], echo: bool = False) -> str
5656

5757

5858
@_async_cache
59-
async def _check_tool_version(name: str, *, echo: bool = False) -> bool:
59+
async def _check_tool_version(
60+
name: str, llvm_version: str, *, echo: bool = False
61+
) -> bool:
6062
output = await _run(name, ["--version"], echo=echo)
61-
return bool(output and _LLVM_VERSION_PATTERN.search(output))
63+
_llvm_version_pattern = re.compile(rf"version\s+{llvm_version}\.\d+\.\d+\S*\s+")
64+
return bool(output and _llvm_version_pattern.search(output))
6265

6366

6467
@_async_cache
65-
async def _get_brew_llvm_prefix(*, echo: bool = False) -> str | None:
66-
output = await _run("brew", ["--prefix", f"llvm@{_LLVM_VERSION}"], echo=echo)
68+
async def _get_brew_llvm_prefix(llvm_version: str, *, echo: bool = False) -> str | None:
69+
output = await _run("brew", ["--prefix", f"llvm@{llvm_version}"], echo=echo)
6770
return output and output.removesuffix("\n")
6871

6972

7073
@_async_cache
71-
async def _find_tool(tool: str, *, echo: bool = False) -> str | None:
74+
async def _find_tool(tool: str, llvm_version: str, *, echo: bool = False) -> str | None:
7275
# Unversioned executables:
7376
path = tool
74-
if await _check_tool_version(path, echo=echo):
77+
if await _check_tool_version(path, llvm_version, echo=echo):
7578
return path
7679
# Versioned executables:
77-
path = f"{tool}-{_LLVM_VERSION}"
78-
if await _check_tool_version(path, echo=echo):
80+
path = f"{tool}-{llvm_version}"
81+
if await _check_tool_version(path, llvm_version, echo=echo):
7982
return path
8083
# PCbuild externals:
8184
externals = os.environ.get("EXTERNALS_DIR", _targets.EXTERNALS)
8285
path = os.path.join(externals, _EXTERNALS_LLVM_TAG, "bin", tool)
83-
if await _check_tool_version(path, echo=echo):
86+
if await _check_tool_version(path, llvm_version, echo=echo):
8487
return path
8588
# Homebrew-installed executables:
86-
prefix = await _get_brew_llvm_prefix(echo=echo)
89+
prefix = await _get_brew_llvm_prefix(llvm_version, echo=echo)
8790
if prefix is not None:
8891
path = os.path.join(prefix, "bin", tool)
89-
if await _check_tool_version(path, echo=echo):
92+
if await _check_tool_version(path, llvm_version, echo=echo):
9093
return path
9194
# Nothing found:
9295
return None
9396

9497

9598
async def maybe_run(
96-
tool: str, args: typing.Iterable[str], echo: bool = False
99+
tool: str,
100+
args: typing.Iterable[str],
101+
echo: bool = False,
102+
llvm_version: str = _LLVM_VERSION,
97103
) -> str | None:
98104
"""Run an LLVM tool if it can be found. Otherwise, return None."""
99-
path = await _find_tool(tool, echo=echo)
105+
106+
path = await _find_tool(tool, llvm_version, echo=echo)
100107
return path and await _run(path, args, echo=echo)
101108

102109

103-
async def run(tool: str, args: typing.Iterable[str], echo: bool = False) -> str:
110+
async def run(
111+
tool: str,
112+
args: typing.Iterable[str],
113+
echo: bool = False,
114+
llvm_version: str = _LLVM_VERSION,
115+
) -> str:
104116
"""Run an LLVM tool if it can be found. Otherwise, raise RuntimeError."""
105-
output = await maybe_run(tool, args, echo=echo)
117+
118+
output = await maybe_run(tool, args, echo=echo, llvm_version=llvm_version)
106119
if output is None:
107-
raise RuntimeError(f"Can't find {tool}-{_LLVM_VERSION}!")
120+
raise RuntimeError(f"Can't find {tool}-{llvm_version}!")
108121
return output

Tools/jit/_targets.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class _Target(typing.Generic[_S, _R]):
5050
debug: bool = False
5151
verbose: bool = False
5252
cflags: str = ""
53+
llvm_version: str = _llvm._LLVM_VERSION
5354
known_symbols: dict[str, int] = dataclasses.field(default_factory=dict)
5455
pyconfig_dir: pathlib.Path = pathlib.Path.cwd().resolve()
5556

@@ -81,7 +82,9 @@ def _compute_digest(self) -> str:
8182
async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup:
8283
group = _stencils.StencilGroup()
8384
args = ["--disassemble", "--reloc", f"{path}"]
84-
output = await _llvm.maybe_run("llvm-objdump", args, echo=self.verbose)
85+
output = await _llvm.maybe_run(
86+
"llvm-objdump", args, echo=self.verbose, llvm_version=self.llvm_version
87+
)
8588
if output is not None:
8689
# Make sure that full paths don't leak out (for reproducibility):
8790
long, short = str(path), str(path.name)
@@ -99,7 +102,9 @@ async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup:
99102
"--sections",
100103
f"{path}",
101104
]
102-
output = await _llvm.run("llvm-readobj", args, echo=self.verbose)
105+
output = await _llvm.run(
106+
"llvm-readobj", args, echo=self.verbose, llvm_version=self.llvm_version
107+
)
103108
# --elf-output-style=JSON is only *slightly* broken on Mach-O...
104109
output = output.replace("PrivateExtern\n", "\n")
105110
output = output.replace("Extern\n", "\n")
@@ -175,12 +180,16 @@ async def _compile(
175180
# Allow user-provided CFLAGS to override any defaults
176181
*shlex.split(self.cflags),
177182
]
178-
await _llvm.run("clang", args_s, echo=self.verbose)
183+
await _llvm.run(
184+
"clang", args_s, echo=self.verbose, llvm_version=self.llvm_version
185+
)
179186
self.optimizer(
180187
s, label_prefix=self.label_prefix, symbol_prefix=self.symbol_prefix
181188
).run()
182189
args_o = [f"--target={self.triple}", "-c", "-o", f"{o}", f"{s}"]
183-
await _llvm.run("clang", args_o, echo=self.verbose)
190+
await _llvm.run(
191+
"clang", args_o, echo=self.verbose, llvm_version=self.llvm_version
192+
)
184193
return await self._parse(o)
185194

186195
async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
@@ -224,6 +233,8 @@ def build(
224233
if not self.stable:
225234
warning = f"JIT support for {self.triple} is still experimental!"
226235
request = "Please report any issues you encounter.".center(len(warning))
236+
if self.llvm_version != _llvm._LLVM_VERSION:
237+
request = f"Warning! Building with an LLVM version other than {_llvm._LLVM_VERSION} is not supported."
227238
outline = "=" * len(warning)
228239
print("\n".join(["", outline, warning, request, outline, ""]))
229240
digest = f"// {self._compute_digest()}\n"

Tools/jit/build.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@
4242
parser.add_argument(
4343
"--cflags", help="additional flags to pass to the compiler", default=""
4444
)
45+
parser.add_argument("--llvm-version", help="LLVM version to use")
4546
args = parser.parse_args()
4647
for target in args.target:
4748
target.debug = args.debug
4849
target.force = args.force
4950
target.verbose = args.verbose
5051
target.cflags = args.cflags
5152
target.pyconfig_dir = args.pyconfig_dir
53+
if args.llvm_version:
54+
target.llvm_version = args.llvm_version
5255
target.build(
5356
comment=comment,
5457
force=args.force,

configure

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2786,7 +2786,7 @@ AS_VAR_IF([jit_flags],
27862786
[],
27872787
[AS_VAR_APPEND([CFLAGS_NODIST], [" $jit_flags"])
27882788
AS_VAR_SET([REGEN_JIT_COMMAND],
2789-
["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\""])
2789+
["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\" --llvm-version=\"$LLVM_VERSION\""])
27902790
AS_VAR_IF([Py_DEBUG],
27912791
[true],
27922792
[AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])],

0 commit comments

Comments
 (0)