#!/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 """ ) )