Skip to content

Commit 5eaef8e

Browse files
committed
JIT compiler: Support 19 bit branch instructions on AArch64 for Mach-O.
Insert labels into assembly for custom relocation during stencil creation.
1 parent f188385 commit 5eaef8e

File tree

4 files changed

+65
-7
lines changed

4 files changed

+65
-7
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add special labels to the assembly created during stencil creation to
2+
support relocations that the native object file format does not support.
3+
Specifically, 19 bit branches for AArch64 in Mach-O object files.

Tools/jit/_optimizers.py

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
_RE_NEVER_MATCH = re.compile(r"(?!)")
1010
# Dictionary mapping branch instructions to their inverted branch instructions.
1111
# If a branch cannot be inverted, the value is None:
12-
_X86_BRANCHES = {
12+
_X86_BRANCH_NAMES = {
1313
# https://www.felixcloutier.com/x86/jcc
1414
"ja": "jna",
1515
"jae": "jnae",
@@ -37,7 +37,11 @@
3737
"loopz": None,
3838
}
3939
# Update with all of the inverted branches, too:
40-
_X86_BRANCHES |= {v: k for k, v in _X86_BRANCHES.items() if v}
40+
_X86_BRANCH_NAMES |= {v: k for k, v in _X86_BRANCH_NAMES.items() if v}
41+
# No custom relocations needed
42+
_X86_BRANCHES: dict[str, tuple[str | None, str | None]] = {
43+
k: (v, None) for k, v in _X86_BRANCH_NAMES.items()
44+
}
4145

4246
_AARCH64_COND_CODES = {
4347
# https://developer.arm.com/documentation/dui0801/b/CJAJIHAD?lang=en
@@ -58,12 +62,15 @@
5862
"hi": "ls",
5963
"ls": "hi",
6064
}
65+
# MyPy doesn't understand that a invariant variable can be initialized by a covariant value
66+
CUSTOM_AARCH64_BRANCH19: str | None = "CUSTOM_AARCH64_BRANCH19"
67+
6168
# Branches are either b.{cond} or bc.{cond}
62-
_AARCH64_BRANCHES = {
63-
"b." + cond: ("b." + inverse if inverse else None)
69+
_AARCH64_BRANCHES: dict[str, tuple[str | None, str | None]] = {
70+
"b." + cond: (("b." + inverse if inverse else None), CUSTOM_AARCH64_BRANCH19)
6471
for (cond, inverse) in _AARCH64_COND_CODES.items()
6572
} | {
66-
"bc." + cond: ("bc." + inverse if inverse else None)
73+
"bc." + cond: (("bc." + inverse if inverse else None), CUSTOM_AARCH64_BRANCH19)
6774
for (cond, inverse) in _AARCH64_COND_CODES.items()
6875
}
6976

@@ -113,7 +120,8 @@ class Optimizer:
113120
r'\s*(?P<label>[\w."$?@]+):'
114121
)
115122
# Override everything that follows in subclasses:
116-
_branches: typing.ClassVar[dict[str, str | None]] = {}
123+
_supports_external_relocations = True
124+
_branches: typing.ClassVar[dict[str, tuple[str | None, str | None]]] = {}
117125
# Two groups (instruction and target):
118126
_re_branch: typing.ClassVar[re.Pattern[str]] = _RE_NEVER_MATCH
119127
# One group (target):
@@ -170,7 +178,10 @@ def _preprocess(self, text: str) -> str:
170178
def _invert_branch(cls, line: str, target: str) -> str | None:
171179
match = cls._re_branch.match(line)
172180
assert match
173-
inverted = cls._branches.get(match["instruction"])
181+
inverted_reloc = cls._branches.get(match["instruction"])
182+
if inverted_reloc is None:
183+
return None
184+
inverted = inverted_reloc[0]
174185
if not inverted:
175186
return None
176187
(a, b), (c, d) = match.span("instruction"), match.span("target")
@@ -302,27 +313,58 @@ def _remove_redundant_jumps(self) -> None:
302313
block.fallthrough = True
303314
block.instructions.pop()
304315

316+
def _fixup_external_labels(self) -> None:
317+
for block in self._blocks():
318+
if block.target and block.fallthrough:
319+
branch = block.instructions[-1]
320+
match = self._re_branch.match(branch)
321+
assert match is not None
322+
target = match["target"]
323+
reloc = self._branches[match["instruction"]][1]
324+
if reloc is not None and not target.startswith(self.label_prefix):
325+
name = target[len(self.symbol_prefix) :]
326+
block.instructions[-1] = (
327+
f"// target='{target}' prefix='{self.label_prefix}'"
328+
)
329+
block.instructions.append(
330+
f"{self.symbol_prefix}{reloc}_JIT_RELOCATION_{name}:"
331+
)
332+
a, b = match.span("target")
333+
branch = "".join([branch[:a], "0", branch[b:]])
334+
block.instructions.append(branch)
335+
305336
def run(self) -> None:
306337
"""Run this optimizer."""
307338
self._insert_continue_label()
308339
self._mark_hot_blocks()
309340
self._invert_hot_branches()
310341
self._remove_redundant_jumps()
342+
if not self._supports_external_relocations:
343+
self._fixup_external_labels()
311344
self.path.write_text(self._body())
312345

313346

314347
# Mach-O does not support the 19 bit branch locations needed for branch reordering
315348
class OptimizerAArch64_MachO(Optimizer): # pylint: disable = too-few-public-methods
316349
"""aarch64-apple-darwin"""
317350

351+
_branches = _AARCH64_BRANCHES
352+
_supports_external_relocations = False
353+
_re_branch = re.compile(
354+
rf"\s*(?P<instruction>{'|'.join(_AARCH64_BRANCHES)})\s+(.+,\s+)*(?P<target>[\w.]+)"
355+
)
356+
318357
# https://developer.arm.com/documentation/ddi0602/2025-03/Base-Instructions/B--Branch-
319358
_re_jump = re.compile(r"\s*b\s+(?P<target>[\w.]+)")
359+
# https://developer.arm.com/documentation/ddi0602/2025-09/Base-Instructions/RET--Return-from-subroutine-
360+
_re_return = re.compile(r"\s*ret\b")
320361

321362

322363
class OptimizerAArch64(Optimizer): # pylint: disable = too-few-public-methods
323364
"""aarch64-pc-windows-msvc/aarch64-unknown-linux-gnu"""
324365

325366
_branches = _AARCH64_BRANCHES
367+
_supports_external_relocations = False
326368
_re_branch = re.compile(
327369
rf"\s*(?P<instruction>{'|'.join(_AARCH64_BRANCHES)})\s+(.+,\s+)*(?P<target>[\w.]+)"
328370
)

Tools/jit/_stencils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class HoleValue(enum.Enum):
5858
"ARM64_RELOC_PAGE21": "patch_aarch64_21r",
5959
"ARM64_RELOC_PAGEOFF12": "patch_aarch64_12",
6060
"ARM64_RELOC_UNSIGNED": "patch_64",
61+
"CUSTOM_AARCH64_BRANCH19": "patch_aarch64_19r",
6162
# x86_64-pc-windows-msvc:
6263
"IMAGE_REL_AMD64_REL32": "patch_x86_64_32rx",
6364
# aarch64-pc-windows-msvc:
@@ -221,6 +222,17 @@ class StencilGroup:
221222
_got: dict[str, int] = dataclasses.field(default_factory=dict, init=False)
222223
_trampolines: set[int] = dataclasses.field(default_factory=set, init=False)
223224

225+
def convert_labels_to_relocations(self) -> None:
226+
for name, hole_plus in self.symbols.items():
227+
if isinstance(name, str) and "_JIT_RELOCATION_" in name:
228+
_, offset = hole_plus
229+
reloc, target = name.split("_JIT_RELOCATION_")
230+
value, symbol = symbol_to_value(target)
231+
hole = Hole(
232+
int(offset), typing.cast(_schema.HoleKind, reloc), value, symbol, 0
233+
)
234+
self.code.holes.append(hole)
235+
224236
def process_relocations(self, known_symbols: dict[str, int]) -> None:
225237
"""Fix up all GOT and internal relocations for this stencil group."""
226238
for hole in self.code.holes.copy():

Tools/jit/_targets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ async def _build_stencils(self) -> dict[str, _stencils.StencilGroup]:
218218
tasks.append(group.create_task(coro, name=opname))
219219
stencil_groups = {task.get_name(): task.result() for task in tasks}
220220
for stencil_group in stencil_groups.values():
221+
stencil_group.convert_labels_to_relocations()
221222
stencil_group.process_relocations(self.known_symbols)
222223
return stencil_groups
223224

0 commit comments

Comments
 (0)