Skip to content

Commit 5a3f479

Browse files
xhochyblurb-it[bot]zoobasavannahostrowski
authored
gh-138451: Support custom LLVM installation path (#138452)
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Steve Dower <steve.dower@microsoft.com> Co-authored-by: Savannah Ostrowski <savannah@python.org>
1 parent 3fd61b7 commit 5a3f479

File tree

8 files changed

+59
-12
lines changed

8 files changed

+59
-12
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow for custom LLVM path using ``LLVM_TOOLS_INSTALL_DIR`` during JIT build.

PCbuild/regen.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@
129129
<JITArgs Condition="$(Platform) == 'x64'">x86_64-pc-windows-msvc</JITArgs>
130130
<JITArgs Condition="$(Configuration) == 'Debug'">$(JITArgs) --debug</JITArgs>
131131
</PropertyGroup>
132-
<Exec Command='$(PythonForBuild) "$(PySourcePath)Tools\jit\build.py" $(JITArgs) --output-dir "$(GeneratedJitStencilsDir)" --pyconfig-dir "$(PySourcePath)PC"'/>
132+
<Exec Command='$(PythonForBuild) "$(PySourcePath)Tools\jit\build.py" $(JITArgs) --output-dir "$(GeneratedJitStencilsDir)" --pyconfig-dir "$(PySourcePath)PC" --llvm-version="$(LLVM_VERSION)" --llvm-tools-install-dir="$(LLVM_TOOLS_INSTALL_DIR)"'/>
133133
</Target>
134134
<Target Name="_CleanJIT" AfterTargets="Clean">
135135
<Delete Files="@(_JITOutputs)"/>

Tools/jit/README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ 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 21 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.
12+
LLVM version 21 is the officially supported version. Both `clang` and `llvm-readobj` need to be installed and discoverable (version suffixes, like `clang-21`, 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.
13+
14+
You can customize the LLVM configuration using environment variables before running configure:
15+
16+
- LLVM_VERSION: Specify a different LLVM version (default: 21)
17+
- LLVM_TOOLS_INSTALL_DIR: Point to a specific LLVM installation prefix when multiple installations exist (the tools are expected in `<dir>/bin`)
1318

1419
It's easy to install all of the required tools:
1520

@@ -62,7 +67,7 @@ choco install llvm --version=21.1.0
6267

6368
### Dev Containers
6469

65-
If you are working on CPython in a [Codespaces instance](https://devguide.python.org/getting-started/setup-building/#using-codespaces), there's no
70+
If you are working on CPython in a [Codespaces instance](https://devguide.python.org/getting-started/setup-building/#using-codespaces), there's no
6671
need to install LLVM as the Fedora 43 base image includes LLVM 21 out of the box.
6772

6873
## Building

Tools/jit/_llvm.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,18 @@ async def _get_brew_llvm_prefix(llvm_version: str, *, echo: bool = False) -> str
8080

8181

8282
@_async_cache
83-
async def _find_tool(tool: str, llvm_version: str, *, echo: bool = False) -> str | None:
83+
async def _find_tool(
84+
tool: str,
85+
llvm_version: str,
86+
llvm_tools_install_dir: str | None,
87+
*,
88+
echo: bool = False,
89+
) -> str | None:
90+
# Explicitly defined LLVM installation location
91+
if llvm_tools_install_dir:
92+
path = os.path.join(llvm_tools_install_dir, "bin", tool)
93+
if await _check_tool_version(path, llvm_version, echo=echo):
94+
return path
8495
# Unversioned executables:
8596
path = tool
8697
if await _check_tool_version(path, llvm_version, echo=echo):
@@ -114,10 +125,11 @@ async def maybe_run(
114125
args: typing.Iterable[str],
115126
echo: bool = False,
116127
llvm_version: str = _LLVM_VERSION,
128+
llvm_tools_install_dir: str | None = None,
117129
) -> str | None:
118130
"""Run an LLVM tool if it can be found. Otherwise, return None."""
119131

120-
path = await _find_tool(tool, llvm_version, echo=echo)
132+
path = await _find_tool(tool, llvm_version, llvm_tools_install_dir, echo=echo)
121133
return path and await _run(path, args, echo=echo)
122134

123135

@@ -126,10 +138,17 @@ async def run(
126138
args: typing.Iterable[str],
127139
echo: bool = False,
128140
llvm_version: str = _LLVM_VERSION,
141+
llvm_tools_install_dir: str | None = None,
129142
) -> str:
130143
"""Run an LLVM tool if it can be found. Otherwise, raise RuntimeError."""
131144

132-
output = await maybe_run(tool, args, echo=echo, llvm_version=llvm_version)
145+
output = await maybe_run(
146+
tool,
147+
args,
148+
echo=echo,
149+
llvm_version=llvm_version,
150+
llvm_tools_install_dir=llvm_tools_install_dir,
151+
)
133152
if output is None:
134153
raise RuntimeError(f"Can't find {tool}-{llvm_version}!")
135154
return output

Tools/jit/_targets.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ class _Target(typing.Generic[_S, _R]):
5353
cflags: str = ""
5454
frame_pointers: bool = False
5555
llvm_version: str = _llvm._LLVM_VERSION
56+
llvm_tools_install_dir: str | None = None
5657
known_symbols: dict[str, int] = dataclasses.field(default_factory=dict)
5758
pyconfig_dir: pathlib.Path = pathlib.Path.cwd().resolve()
5859

@@ -85,7 +86,11 @@ async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup:
8586
group = _stencils.StencilGroup()
8687
args = ["--disassemble", "--reloc", f"{path}"]
8788
output = await _llvm.maybe_run(
88-
"llvm-objdump", args, echo=self.verbose, llvm_version=self.llvm_version
89+
"llvm-objdump",
90+
args,
91+
echo=self.verbose,
92+
llvm_version=self.llvm_version,
93+
llvm_tools_install_dir=self.llvm_tools_install_dir,
8994
)
9095
if output is not None:
9196
# Make sure that full paths don't leak out (for reproducibility):
@@ -105,7 +110,11 @@ async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup:
105110
f"{path}",
106111
]
107112
output = await _llvm.run(
108-
"llvm-readobj", args, echo=self.verbose, llvm_version=self.llvm_version
113+
"llvm-readobj",
114+
args,
115+
echo=self.verbose,
116+
llvm_version=self.llvm_version,
117+
llvm_tools_install_dir=self.llvm_tools_install_dir,
109118
)
110119
# --elf-output-style=JSON is only *slightly* broken on Mach-O...
111120
output = output.replace("PrivateExtern\n", "\n")
@@ -184,7 +193,11 @@ async def _compile(
184193
# Allow user-provided CFLAGS to override any defaults
185194
args_s += shlex.split(self.cflags)
186195
await _llvm.run(
187-
"clang", args_s, echo=self.verbose, llvm_version=self.llvm_version
196+
"clang",
197+
args_s,
198+
echo=self.verbose,
199+
llvm_version=self.llvm_version,
200+
llvm_tools_install_dir=self.llvm_tools_install_dir,
188201
)
189202
if not is_shim:
190203
self.optimizer(
@@ -196,7 +209,11 @@ async def _compile(
196209
).run()
197210
args_o = [f"--target={self.triple}", "-c", "-o", f"{o}", f"{s}"]
198211
await _llvm.run(
199-
"clang", args_o, echo=self.verbose, llvm_version=self.llvm_version
212+
"clang",
213+
args_o,
214+
echo=self.verbose,
215+
llvm_version=self.llvm_version,
216+
llvm_tools_install_dir=self.llvm_tools_install_dir,
200217
)
201218
return await self._parse(o)
202219

Tools/jit/build.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
"--cflags", help="additional flags to pass to the compiler", default=""
4444
)
4545
parser.add_argument("--llvm-version", help="LLVM version to use")
46+
parser.add_argument(
47+
"--llvm-tools-install-dir", help="Installation location of LLVM tools"
48+
)
4649
args = parser.parse_args()
4750
for target in args.target:
4851
target.debug = args.debug
@@ -52,6 +55,8 @@
5255
target.pyconfig_dir = args.pyconfig_dir
5356
if args.llvm_version:
5457
target.llvm_version = args.llvm_version
58+
if args.llvm_tools_install_dir:
59+
target.llvm_tools_install_dir = args.llvm_tools_install_dir
5560
target.build(
5661
comment=comment,
5762
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
@@ -2850,7 +2850,7 @@ AS_VAR_IF([jit_flags],
28502850
[],
28512851
[AS_VAR_APPEND([CFLAGS_NODIST], [" $jit_flags"])
28522852
AS_VAR_SET([REGEN_JIT_COMMAND],
2853-
["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\" --llvm-version=\"$LLVM_VERSION\""])
2853+
["\$(PYTHON_FOR_REGEN) \$(srcdir)/Tools/jit/build.py ${ARCH_TRIPLES:-$host} --output-dir . --pyconfig-dir . --cflags=\"$CFLAGS_JIT\" --llvm-version=\"$LLVM_VERSION\" --llvm-tools-install-dir=\"$LLVM_TOOLS_INSTALL_DIR\""])
28542854
AS_VAR_IF([Py_DEBUG],
28552855
[true],
28562856
[AS_VAR_APPEND([REGEN_JIT_COMMAND], [" --debug"])],

0 commit comments

Comments
 (0)