Skip to content

Commit 52e7fa3

Browse files
committed
Revert "pythonGH-125498: Update JIT builds to use LLVM 19 and preserve_none (pythonGH-125499)"
This reverts commit c29bbe2.
1 parent 5ea3759 commit 52e7fa3

File tree

11 files changed

+78
-69
lines changed

11 files changed

+78
-69
lines changed

.github/workflows/jit.yml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
- true
6262
- false
6363
llvm:
64-
- 19
64+
- 18
6565
include:
6666
- target: i686-pc-windows-msvc/msvc
6767
architecture: Win32
@@ -121,15 +121,10 @@ jobs:
121121
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}.1.0
122122
./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '' }} -p ${{ matrix.architecture }}
123123
124-
# The `find` line is required as a result of https://github.com/actions/runner-images/issues/9966.
125-
# This is a bug in the macOS runner image where the pre-installed Python is installed in the same
126-
# directory as the Homebrew Python, which causes the build to fail for macos-13. This line removes
127-
# the symlink to the pre-installed Python so that the Homebrew Python is used instead.
128124
- name: Native macOS
129125
if: runner.os == 'macOS'
130126
run: |
131127
brew update
132-
find /usr/local/bin -lname '*/Library/Frameworks/Python.framework/*' -delete
133128
brew install llvm@${{ matrix.llvm }}
134129
SDKROOT="$(xcrun --show-sdk-path)" \
135130
./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }}
@@ -170,19 +165,15 @@ jobs:
170165
name: Free-Threaded (Debug)
171166
needs: interpreter
172167
runs-on: ubuntu-latest
173-
strategy:
174-
matrix:
175-
llvm:
176-
- 19
177168
steps:
178169
- uses: actions/checkout@v4
179170
- uses: actions/setup-python@v5
180171
with:
181172
python-version: '3.11'
182173
- name: Build with JIT enabled and GIL disabled
183174
run: |
184-
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
185-
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
175+
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh 18
176+
export PATH="$(llvm-config-18 --bindir):$PATH"
186177
./configure --enable-experimental-jit --with-pydebug --disable-gil
187178
make all --jobs 4
188179
- name: Run tests

Misc/NEWS.d/next/Core_and_Builtins/2024-09-14-20-09-39.gh-issue-123714.o1mbe4.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.

Misc/NEWS.d/next/Core_and_Builtins/2024-10-22-04-18-53.gh-issue-125498.cFjPIn.rst

Lines changed: 0 additions & 4 deletions
This file was deleted.

Tools/jit/README.md

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,49 @@ This version of CPython can be built with an experimental just-in-time compiler[
77

88
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).
99

10-
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.
10+
LLVM version 18 is required. Both `clang` and `llvm-readobj` need to be installed and discoverable (version suffixes, like `clang-18`, 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.
1111

1212
It's easy to install all of the required tools:
1313

1414
### Linux
1515

16-
Install LLVM 19 on Ubuntu/Debian:
16+
Install LLVM 18 on Ubuntu/Debian:
1717

1818
```sh
1919
wget https://apt.llvm.org/llvm.sh
2020
chmod +x llvm.sh
21-
sudo ./llvm.sh 19
21+
sudo ./llvm.sh 18
2222
```
2323

24-
Install LLVM 19 on Fedora Linux 40 or newer:
24+
Install LLVM 18 on Fedora Linux 40 or newer:
2525

2626
```sh
27-
sudo dnf install 'clang(major) = 19' 'llvm(major) = 19'
27+
sudo dnf install 'clang(major) = 18' 'llvm(major) = 18'
2828
```
2929

3030
### macOS
3131

32-
Install LLVM 19 with [Homebrew](https://brew.sh):
32+
Install LLVM 18 with [Homebrew](https://brew.sh):
3333

3434
```sh
35-
brew install llvm@19
35+
brew install llvm@18
3636
```
3737

3838
Homebrew won't add any of the tools to your `$PATH`. That's okay; the build script knows how to find them.
3939

4040
### Windows
4141

42-
Install LLVM 19 [by searching for it on LLVM's GitHub releases page](https://github.com/llvm/llvm-project/releases?q=19), clicking on "Assets", downloading the appropriate Windows installer for your platform (likely the file ending with `-win64.exe`), and running it. **When installing, be sure to select the option labeled "Add LLVM to the system PATH".**
42+
Install LLVM 18 [by searching for it on LLVM's GitHub releases page](https://github.com/llvm/llvm-project/releases?q=18), clicking on "Assets", downloading the appropriate Windows installer for your platform (likely the file ending with `-win64.exe`), and running it. **When installing, be sure to select the option labeled "Add LLVM to the system PATH".**
4343

4444
Alternatively, you can use [chocolatey](https://chocolatey.org):
4545

4646
```sh
47-
choco install llvm --version=19.1.0
47+
choco install llvm --version=18.1.6
4848
```
4949

50+
### Dev Containers
51+
52+
If you are working CPython in a [Codespaces instance](https://devguide.python.org/getting-started/setup-building/#using-codespaces), there's no need to install LLVM as the Fedora 40 base image includes LLVM 18 out of the box.
5053

5154
## Building
5255

Tools/jit/_llvm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import subprocess
99
import typing
1010

11-
_LLVM_VERSION = 19
11+
_LLVM_VERSION = 18
1212
_LLVM_VERSION_PATTERN = re.compile(rf"version\s+{_LLVM_VERSION}\.\d+\.\d+\S*\s+")
1313

1414
_P = typing.ParamSpec("_P")

Tools/jit/_stencils.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import dataclasses
44
import enum
5-
import sys
65
import typing
76

87
import _schema
@@ -133,26 +132,15 @@ class Hole:
133132
def __post_init__(self) -> None:
134133
self.func = _PATCH_FUNCS[self.kind]
135134

136-
def fold(self, other: typing.Self, body: bytes) -> typing.Self | None:
135+
def fold(self, other: typing.Self) -> typing.Self | None:
137136
"""Combine two holes into a single hole, if possible."""
138-
instruction_a = int.from_bytes(
139-
body[self.offset : self.offset + 4], byteorder=sys.byteorder
140-
)
141-
instruction_b = int.from_bytes(
142-
body[other.offset : other.offset + 4], byteorder=sys.byteorder
143-
)
144-
reg_a = instruction_a & 0b11111
145-
reg_b1 = instruction_b & 0b11111
146-
reg_b2 = (instruction_b >> 5) & 0b11111
147-
148137
if (
149138
self.offset + 4 == other.offset
150139
and self.value == other.value
151140
and self.symbol == other.symbol
152141
and self.addend == other.addend
153142
and self.func == "patch_aarch64_21rx"
154143
and other.func == "patch_aarch64_12x"
155-
and reg_a == reg_b1 == reg_b2
156144
):
157145
# These can *only* be properly relaxed when they appear together and
158146
# patch the same value:

Tools/jit/_targets.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
PYTHON_EXECUTOR_CASES_C_H = CPYTHON / "Python" / "executor_cases.c.h"
2727
TOOLS_JIT_TEMPLATE_C = TOOLS_JIT / "template.c"
2828

29+
2930
_S = typing.TypeVar("_S", _schema.COFFSection, _schema.ELFSection, _schema.MachOSection)
3031
_R = typing.TypeVar(
3132
"_R", _schema.COFFRelocation, _schema.ELFRelocation, _schema.MachORelocation
@@ -38,6 +39,7 @@ class _Target(typing.Generic[_S, _R]):
3839
_: dataclasses.KW_ONLY
3940
alignment: int = 1
4041
args: typing.Sequence[str] = ()
42+
ghccc: bool = False
4143
prefix: str = ""
4244
stable: bool = False
4345
debug: bool = False
@@ -86,7 +88,11 @@ async def _parse(self, path: pathlib.Path) -> _stencils.StencilGroup:
8688
sections: list[dict[typing.Literal["Section"], _S]] = json.loads(output)
8789
for wrapped_section in sections:
8890
self._handle_section(wrapped_section["Section"], group)
89-
assert group.symbols["_JIT_ENTRY"] == (_stencils.HoleValue.CODE, 0)
91+
# The trampoline's entry point is just named "_ENTRY", since on some
92+
# platforms we later assume that any function starting with "_JIT_" uses
93+
# the GHC calling convention:
94+
entry_symbol = "_JIT_ENTRY" if "_JIT_ENTRY" in group.symbols else "_ENTRY"
95+
assert group.symbols[entry_symbol] == (_stencils.HoleValue.CODE, 0)
9096
if group.data.body:
9197
line = f"0: {str(bytes(group.data.body)).removeprefix('b')}"
9298
group.data.disassembly.append(line)
@@ -106,6 +112,9 @@ def _handle_relocation(
106112
async def _compile(
107113
self, opname: str, c: pathlib.Path, tempdir: pathlib.Path
108114
) -> _stencils.StencilGroup:
115+
# "Compile" the trampoline to an empty stencil group if it's not needed:
116+
if opname == "trampoline" and not self.ghccc:
117+
return _stencils.StencilGroup()
109118
o = tempdir / f"{opname}.o"
110119
args = [
111120
f"--target={self.triple}",
@@ -119,7 +128,6 @@ async def _compile(
119128
f"-I{CPYTHON / 'Include' / 'internal'}",
120129
f"-I{CPYTHON / 'Include' / 'internal' / 'mimalloc'}",
121130
f"-I{CPYTHON / 'Python'}",
122-
f"-I{CPYTHON / 'Tools' / 'jit'}",
123131
"-O3",
124132
"-c",
125133
# This debug info isn't necessary, and bloats out the JIT'ed code.
@@ -135,12 +143,44 @@ async def _compile(
135143
# Don't call stack-smashing canaries that we can't find or patch:
136144
"-fno-stack-protector",
137145
"-std=c11",
138-
"-o",
139-
f"{o}",
140-
f"{c}",
141146
*self.args,
142147
]
143-
await _llvm.run("clang", args, echo=self.verbose)
148+
if self.ghccc:
149+
# This is a bit of an ugly workaround, but it makes the code much
150+
# smaller and faster, so it's worth it. We want to use the GHC
151+
# calling convention, but Clang doesn't support it. So, we *first*
152+
# compile the code to LLVM IR, perform some text replacements on the
153+
# IR to change the calling convention(!), and then compile *that*.
154+
# Once we have access to Clang 19, we can get rid of this and use
155+
# __attribute__((preserve_none)) directly in the C code instead:
156+
ll = tempdir / f"{opname}.ll"
157+
args_ll = args + [
158+
# -fomit-frame-pointer is necessary because the GHC calling
159+
# convention uses RBP to pass arguments:
160+
"-S",
161+
"-emit-llvm",
162+
"-fomit-frame-pointer",
163+
"-o",
164+
f"{ll}",
165+
f"{c}",
166+
]
167+
await _llvm.run("clang", args_ll, echo=self.verbose)
168+
ir = ll.read_text()
169+
# This handles declarations, definitions, and calls to named symbols
170+
# starting with "_JIT_":
171+
ir = re.sub(
172+
r"(((noalias|nonnull|noundef) )*ptr @_JIT_\w+\()", r"ghccc \1", ir
173+
)
174+
# This handles calls to anonymous callees, since anything with
175+
# "musttail" needs to use the same calling convention:
176+
ir = ir.replace("musttail call", "musttail call ghccc")
177+
# Sometimes *both* replacements happen at the same site, so fix it:
178+
ir = ir.replace("ghccc ghccc", "ghccc")
179+
ll.write_text(ir)
180+
args_o = args + ["-Wno-unused-command-line-argument", "-o", f"{o}", f"{ll}"]
181+
else:
182+
args_o = args + ["-o", f"{o}", f"{c}"]
183+
await _llvm.run("clang", args_o, echo=self.verbose)
144184
return await self._parse(o)
145185

146186
async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
@@ -479,6 +519,7 @@ def _handle_relocation(
479519

480520
def get_target(host: str) -> _COFF | _ELF | _MachO:
481521
"""Build a _Target for the given host "triple" and options."""
522+
# ghccc currently crashes Clang when combined with musttail on aarch64. :(
482523
target: _COFF | _ELF | _MachO
483524
if re.fullmatch(r"aarch64-apple-darwin.*", host):
484525
target = _MachO(host, alignment=8, prefix="_")
@@ -494,20 +535,16 @@ def get_target(host: str) -> _COFF | _ELF | _MachO:
494535
]
495536
target = _ELF(host, alignment=8, args=args)
496537
elif re.fullmatch(r"i686-pc-windows-msvc", host):
497-
args = [
498-
"-DPy_NO_ENABLE_SHARED",
499-
# __attribute__((preserve_none)) is not supported
500-
"-Wno-ignored-attributes",
501-
]
502-
target = _COFF(host, args=args, prefix="_")
538+
args = ["-DPy_NO_ENABLE_SHARED"]
539+
target = _COFF(host, args=args, ghccc=True, prefix="_")
503540
elif re.fullmatch(r"x86_64-apple-darwin.*", host):
504-
target = _MachO(host, prefix="_")
541+
target = _MachO(host, ghccc=True, prefix="_")
505542
elif re.fullmatch(r"x86_64-pc-windows-msvc", host):
506543
args = ["-fms-runtime-lib=dll"]
507-
target = _COFF(host, args=args)
544+
target = _COFF(host, args=args, ghccc=True)
508545
elif re.fullmatch(r"x86_64-.*-linux-gnu", host):
509546
args = ["-fpic"]
510-
target = _ELF(host, args=args)
547+
target = _ELF(host, args=args, ghccc=True)
511548
else:
512549
raise ValueError(host)
513550
return target

Tools/jit/_writer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _dump_stencil(opname: str, group: _stencils.StencilGroup) -> typing.Iterator
6565
if skip:
6666
skip = False
6767
continue
68-
if pair and (folded := hole.fold(pair, stencil.body)):
68+
if pair and (folded := hole.fold(pair)):
6969
skip = True
7070
hole = folded
7171
yield f" {hole.as_c(part)}"

Tools/jit/jit.h

Lines changed: 0 additions & 4 deletions
This file was deleted.

Tools/jit/template.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121

2222
#include "ceval_macros.h"
2323

24-
#include "jit.h"
25-
2624
#undef CURRENT_OPARG
2725
#define CURRENT_OPARG() (_oparg)
2826

@@ -51,7 +49,7 @@
5149
do { \
5250
OPT_STAT_INC(traces_executed); \
5351
__attribute__((musttail)) \
54-
return ((jit_func_preserve_none)((EXECUTOR)->jit_side_entry))(frame, stack_pointer, tstate); \
52+
return ((jit_func)((EXECUTOR)->jit_side_entry))(frame, stack_pointer, tstate); \
5553
} while (0)
5654

5755
#undef GOTO_TIER_ONE
@@ -74,7 +72,7 @@ do { \
7472
do { \
7573
PyAPI_DATA(void) ALIAS; \
7674
__attribute__((musttail)) \
77-
return ((jit_func_preserve_none)&ALIAS)(frame, stack_pointer, tstate); \
75+
return ((jit_func)&ALIAS)(frame, stack_pointer, tstate); \
7876
} while (0)
7977

8078
#undef JUMP_TO_JUMP_TARGET
@@ -88,7 +86,7 @@ do { \
8886

8987
#define TIER_TWO 2
9088

91-
__attribute__((preserve_none)) _Py_CODEUNIT *
89+
_Py_CODEUNIT *
9290
_JIT_ENTRY(_PyInterpreterFrame *frame, _PyStackRef *stack_pointer, PyThreadState *tstate)
9391
{
9492
// Locals that the instruction implementations expect to exist:

0 commit comments

Comments
 (0)