
Add pre-commit tests for lowering atomicrmw `fminimum`/`fmaximum` to AArch64 assembler, in a subsequent change.
416 lines
12 KiB
Python
Executable File
416 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import textwrap
|
|
import enum
|
|
import os
|
|
import re
|
|
|
|
"""
|
|
Generate the tests in llvm/test/CodeGen/AArch64/Atomics. Run from top level llvm-project.
|
|
"""
|
|
|
|
TRIPLES = [
|
|
"aarch64",
|
|
"aarch64_be",
|
|
]
|
|
|
|
|
|
class ByteSizes:
|
|
def __init__(self, pairs):
|
|
if not isinstance(pairs, list):
|
|
raise ValueError("Must init with a list of key-value pairs")
|
|
|
|
self._data = pairs[:]
|
|
|
|
def __iter__(self):
|
|
return iter(self._data)
|
|
|
|
|
|
# fmt: off
|
|
Type = ByteSizes([
|
|
("i8", 1),
|
|
("i16", 2),
|
|
("i32", 4),
|
|
("i64", 8),
|
|
("i128", 16)])
|
|
|
|
FPType = ByteSizes([
|
|
("half", 2),
|
|
("bfloat", 2),
|
|
("float", 4),
|
|
("double", 8)])
|
|
# fmt: on
|
|
|
|
|
|
# Is this an aligned or unaligned access?
|
|
class Aligned(enum.Enum):
|
|
aligned = True
|
|
unaligned = False
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
def __bool__(self) -> bool:
|
|
return self.value
|
|
|
|
|
|
class AtomicOrder(enum.Enum):
|
|
notatomic = 0
|
|
unordered = 1
|
|
monotonic = 2
|
|
acquire = 3
|
|
release = 4
|
|
acq_rel = 5
|
|
seq_cst = 6
|
|
|
|
def __str__(self) -> str:
|
|
return self.name
|
|
|
|
|
|
ATOMICRMW_ORDERS = [
|
|
AtomicOrder.monotonic,
|
|
AtomicOrder.acquire,
|
|
AtomicOrder.release,
|
|
AtomicOrder.acq_rel,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
ATOMIC_LOAD_ORDERS = [
|
|
AtomicOrder.unordered,
|
|
AtomicOrder.monotonic,
|
|
AtomicOrder.acquire,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
ATOMIC_STORE_ORDERS = [
|
|
AtomicOrder.unordered,
|
|
AtomicOrder.monotonic,
|
|
AtomicOrder.release,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
ATOMIC_FENCE_ORDERS = [
|
|
AtomicOrder.acquire,
|
|
AtomicOrder.release,
|
|
AtomicOrder.acq_rel,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
CMPXCHG_SUCCESS_ORDERS = [
|
|
AtomicOrder.monotonic,
|
|
AtomicOrder.acquire,
|
|
AtomicOrder.release,
|
|
AtomicOrder.acq_rel,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
CMPXCHG_FAILURE_ORDERS = [
|
|
AtomicOrder.monotonic,
|
|
AtomicOrder.acquire,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
FENCE_ORDERS = [
|
|
AtomicOrder.acquire,
|
|
AtomicOrder.release,
|
|
AtomicOrder.acq_rel,
|
|
AtomicOrder.seq_cst,
|
|
]
|
|
|
|
|
|
class Feature(enum.Flag):
|
|
# Feature names in filenames are determined by the spelling here:
|
|
v8a = enum.auto()
|
|
v8_1a = enum.auto() # -mattr=+v8.1a, mandatory FEAT_LOR, FEAT_LSE
|
|
rcpc = enum.auto() # FEAT_LRCPC
|
|
lse2 = enum.auto() # FEAT_LSE2
|
|
outline_atomics = enum.auto() # -moutline-atomics
|
|
rcpc3 = enum.auto() # FEAT_LSE2 + FEAT_LRCPC3
|
|
lse2_lse128 = enum.auto() # FEAT_LSE2 + FEAT_LSE128
|
|
|
|
def test_scope():
|
|
return "all"
|
|
|
|
@property
|
|
def mattr(self):
|
|
if self == Feature.outline_atomics:
|
|
return "+outline-atomics"
|
|
if self == Feature.v8_1a:
|
|
return "+v8.1a"
|
|
if self == Feature.rcpc3:
|
|
return "+lse2,+rcpc3"
|
|
if self == Feature.lse2_lse128:
|
|
return "+lse2,+lse128"
|
|
return "+" + self.name
|
|
|
|
|
|
class FPFeature(enum.Flag):
|
|
# Feature names in filenames are determined by the spelling here:
|
|
v8a_fp = enum.auto()
|
|
lsfe = enum.auto() # FEAT_LSFE
|
|
|
|
def test_scope():
|
|
return "atomicrmw"
|
|
|
|
@property
|
|
def mattr(self):
|
|
if self == FPFeature.v8a_fp:
|
|
return "+v8a"
|
|
return "+" + self.name
|
|
|
|
|
|
ATOMICRMW_OPS = [
|
|
"xchg",
|
|
"add",
|
|
"sub",
|
|
"and",
|
|
"nand",
|
|
"or",
|
|
"xor",
|
|
"max",
|
|
"min",
|
|
"umax",
|
|
"umin",
|
|
]
|
|
|
|
FP_ATOMICRMW_OPS = [
|
|
"fadd",
|
|
"fsub",
|
|
"fmax",
|
|
"fmin",
|
|
"fmaximum",
|
|
"fminimum",
|
|
]
|
|
|
|
|
|
def relpath():
|
|
# __file__ changed to return absolute path in Python 3.9. Print only
|
|
# up to llvm-project (6 levels higher), to avoid unnecessary diffs and
|
|
# revealing directory structure of people running this script
|
|
top = "../" * 6
|
|
fp = os.path.relpath(__file__, os.path.abspath(os.path.join(__file__, top)))
|
|
return fp
|
|
|
|
|
|
def generate_unused_res_test(featname, ordering, op, alignval):
|
|
if featname != "lsfe" or op == "fsub" or alignval == 1:
|
|
return False
|
|
if ordering not in [AtomicOrder.monotonic, AtomicOrder.release]:
|
|
return False
|
|
return True
|
|
|
|
|
|
def align(val, aligned: bool) -> int:
|
|
return val if aligned else 1
|
|
|
|
|
|
def all_atomicrmw(f, datatype, atomicrmw_ops, featname):
|
|
instr = "atomicrmw"
|
|
generate_unused = False
|
|
tests = []
|
|
for op in atomicrmw_ops:
|
|
for aligned in Aligned:
|
|
for ty, val in datatype:
|
|
alignval = align(val, aligned)
|
|
for ordering in ATOMICRMW_ORDERS:
|
|
name = f"atomicrmw_{op}_{ty}_{aligned}_{ordering}"
|
|
tests.append(
|
|
textwrap.dedent(
|
|
f"""
|
|
define dso_local {ty} @{name}(ptr %ptr, {ty} %value) {{
|
|
%r = {instr} {op} ptr %ptr, {ty} %value {ordering}, align {alignval}
|
|
ret {ty} %r
|
|
}}
|
|
"""
|
|
)
|
|
)
|
|
if generate_unused_res_test(featname, ordering, op, alignval):
|
|
generate_unused = True
|
|
name = f"atomicrmw_{op}_{ty}_{aligned}_{ordering}_unused"
|
|
tests.append(
|
|
textwrap.dedent(
|
|
f"""
|
|
define dso_local void @{name}(ptr %ptr, {ty} %value) {{
|
|
%r = {instr} {op} ptr %ptr, {ty} %value {ordering}, align {alignval}
|
|
ret void
|
|
}}
|
|
"""
|
|
)
|
|
)
|
|
|
|
if generate_unused:
|
|
f.write(
|
|
"\n; NOTE: '_unused' tests are added to ensure we do not lower to "
|
|
"ST[F]ADD when the destination register is WZR/XZR.\n"
|
|
"; See discussion on https://github.com/llvm/llvm-project/pull/131174\n"
|
|
)
|
|
|
|
for test in tests:
|
|
f.write(test)
|
|
|
|
|
|
def all_load(f):
|
|
for aligned in Aligned:
|
|
for ty, val in Type:
|
|
alignval = align(val, aligned)
|
|
for ordering in ATOMIC_LOAD_ORDERS:
|
|
for const in [False, True]:
|
|
name = f"load_atomic_{ty}_{aligned}_{ordering}"
|
|
instr = "load atomic"
|
|
if const:
|
|
name += "_const"
|
|
arg = "ptr readonly %ptr" if const else "ptr %ptr"
|
|
f.write(
|
|
textwrap.dedent(
|
|
f"""
|
|
define dso_local {ty} @{name}({arg}) {{
|
|
%r = {instr} {ty}, ptr %ptr {ordering}, align {alignval}
|
|
ret {ty} %r
|
|
}}
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
def all_store(f):
|
|
for aligned in Aligned:
|
|
for ty, val in Type:
|
|
alignval = align(val, aligned)
|
|
for ordering in ATOMIC_STORE_ORDERS: # FIXME stores
|
|
name = f"store_atomic_{ty}_{aligned}_{ordering}"
|
|
instr = "store atomic"
|
|
f.write(
|
|
textwrap.dedent(
|
|
f"""
|
|
define dso_local void @{name}({ty} %value, ptr %ptr) {{
|
|
{instr} {ty} %value, ptr %ptr {ordering}, align {alignval}
|
|
ret void
|
|
}}
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
def all_cmpxchg(f):
|
|
for aligned in Aligned:
|
|
for ty, val in Type:
|
|
alignval = align(val, aligned)
|
|
for success_ordering in CMPXCHG_SUCCESS_ORDERS:
|
|
for failure_ordering in CMPXCHG_FAILURE_ORDERS:
|
|
for weak in [False, True]:
|
|
name = f"cmpxchg_{ty}_{aligned}_{success_ordering}_{failure_ordering}"
|
|
instr = "cmpxchg"
|
|
if weak:
|
|
name += "_weak"
|
|
instr += " weak"
|
|
f.write(
|
|
textwrap.dedent(
|
|
f"""
|
|
define dso_local {ty} @{name}({ty} %expected, {ty} %new, ptr %ptr) {{
|
|
%pair = {instr} ptr %ptr, {ty} %expected, {ty} %new {success_ordering} {failure_ordering}, align {alignval}
|
|
%r = extractvalue {{ {ty}, i1 }} %pair, 0
|
|
ret {ty} %r
|
|
}}
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
def all_fence(f):
|
|
for ordering in FENCE_ORDERS:
|
|
name = f"fence_{ordering}"
|
|
f.write(
|
|
textwrap.dedent(
|
|
f"""
|
|
define dso_local void @{name}() {{
|
|
fence {ordering}
|
|
ret void
|
|
}}
|
|
"""
|
|
)
|
|
)
|
|
|
|
|
|
def header(f, triple, features, filter_args: str):
|
|
f.write(
|
|
"; NOTE: Assertions have been autogenerated by "
|
|
"utils/update_llc_test_checks.py UTC_ARGS: "
|
|
)
|
|
f.write(filter_args)
|
|
f.write("\n")
|
|
f.write(f"; The base test file was generated by ./{relpath()}\n")
|
|
|
|
for feat in features:
|
|
for OptFlag in ["-O0", "-O1"]:
|
|
f.write(
|
|
" ".join(
|
|
[
|
|
";",
|
|
"RUN:",
|
|
"llc",
|
|
"%s",
|
|
"-o",
|
|
"-",
|
|
"-verify-machineinstrs",
|
|
f"-mtriple={triple}",
|
|
f"-mattr={feat.mattr}",
|
|
OptFlag,
|
|
"|",
|
|
"FileCheck",
|
|
"%s",
|
|
f"--check-prefixes=CHECK,{OptFlag}\n",
|
|
]
|
|
)
|
|
)
|
|
|
|
|
|
def write_lit_tests(feature, datatypes, ops):
|
|
for triple in TRIPLES:
|
|
# Feature has no effect on fence, so keep it to one file.
|
|
with open(f"{triple}-fence.ll", "w") as f:
|
|
filter_args = r'--filter "^\s*(dmb)"'
|
|
header(f, triple, Feature, filter_args)
|
|
all_fence(f)
|
|
|
|
for feat in feature:
|
|
with open(f"{triple}-atomicrmw-{feat.name}.ll", "w") as f:
|
|
filter_args = r'--filter-out "\b(sp)\b" --filter "^\s*(ld[^r]|st[^r]|swp|cas|bl|add|and|eor|orn|orr|sub|mvn|sxt|cmp|ccmp|csel|dmb)"'
|
|
header(f, triple, [feat], filter_args)
|
|
all_atomicrmw(f, datatypes, ops, feat.name)
|
|
|
|
# Floating point atomics only supported for atomicrmw currently
|
|
if feature.test_scope() == "atomicrmw":
|
|
continue
|
|
|
|
with open(f"{triple}-cmpxchg-{feat.name}.ll", "w") as f:
|
|
filter_args = r'--filter-out "\b(sp)\b" --filter "^\s*(ld[^r]|st[^r]|swp|cas|bl|add|and|eor|orn|orr|sub|mvn|sxt|cmp|ccmp|csel|dmb)"'
|
|
header(f, triple, [feat], filter_args)
|
|
all_cmpxchg(f)
|
|
|
|
with open(f"{triple}-atomic-load-{feat.name}.ll", "w") as f:
|
|
filter_args = r'--filter-out "\b(sp)\b" --filter "^\s*(ld|st[^r]|swp|cas|bl|add|and|eor|orn|orr|sub|mvn|sxt|cmp|ccmp|csel|dmb)"'
|
|
header(f, triple, [feat], filter_args)
|
|
all_load(f)
|
|
|
|
with open(f"{triple}-atomic-store-{feat.name}.ll", "w") as f:
|
|
filter_args = r'--filter-out "\b(sp)\b" --filter "^\s*(ld[^r]|st|swp|cas|bl|add|and|eor|orn|orr|sub|mvn|sxt|cmp|ccmp|csel|dmb)"'
|
|
header(f, triple, [feat], filter_args)
|
|
all_store(f)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
os.chdir("llvm/test/CodeGen/AArch64/Atomics/")
|
|
write_lit_tests(Feature, Type, ATOMICRMW_OPS)
|
|
write_lit_tests(FPFeature, FPType, FP_ATOMICRMW_OPS)
|
|
|
|
print(
|
|
textwrap.dedent(
|
|
"""
|
|
Testcases written. To update checks run:
|
|
$ ./llvm/utils/update_llc_test_checks.py -u llvm/test/CodeGen/AArch64/Atomics/*.ll
|
|
|
|
Or in parallel:
|
|
$ parallel ./llvm/utils/update_llc_test_checks.py -u ::: llvm/test/CodeGen/AArch64/Atomics/*.ll
|
|
"""
|
|
)
|
|
)
|