416 lines · python
1#!/usr/bin/env python32import textwrap3import enum4import os5import re6 7"""8Generate the tests in llvm/test/CodeGen/AArch64/Atomics. Run from top level llvm-project.9"""10 11TRIPLES = [12 "aarch64",13 "aarch64_be",14]15 16 17class ByteSizes:18 def __init__(self, pairs):19 if not isinstance(pairs, list):20 raise ValueError("Must init with a list of key-value pairs")21 22 self._data = pairs[:]23 24 def __iter__(self):25 return iter(self._data)26 27 28# fmt: off29Type = ByteSizes([30 ("i8", 1),31 ("i16", 2),32 ("i32", 4),33 ("i64", 8),34 ("i128", 16)])35 36FPType = ByteSizes([37 ("half", 2),38 ("bfloat", 2),39 ("float", 4),40 ("double", 8)])41# fmt: on42 43 44# Is this an aligned or unaligned access?45class Aligned(enum.Enum):46 aligned = True47 unaligned = False48 49 def __str__(self) -> str:50 return self.name51 52 def __bool__(self) -> bool:53 return self.value54 55 56class AtomicOrder(enum.Enum):57 notatomic = 058 unordered = 159 monotonic = 260 acquire = 361 release = 462 acq_rel = 563 seq_cst = 664 65 def __str__(self) -> str:66 return self.name67 68 69ATOMICRMW_ORDERS = [70 AtomicOrder.monotonic,71 AtomicOrder.acquire,72 AtomicOrder.release,73 AtomicOrder.acq_rel,74 AtomicOrder.seq_cst,75]76 77ATOMIC_LOAD_ORDERS = [78 AtomicOrder.unordered,79 AtomicOrder.monotonic,80 AtomicOrder.acquire,81 AtomicOrder.seq_cst,82]83 84ATOMIC_STORE_ORDERS = [85 AtomicOrder.unordered,86 AtomicOrder.monotonic,87 AtomicOrder.release,88 AtomicOrder.seq_cst,89]90 91ATOMIC_FENCE_ORDERS = [92 AtomicOrder.acquire,93 AtomicOrder.release,94 AtomicOrder.acq_rel,95 AtomicOrder.seq_cst,96]97 98CMPXCHG_SUCCESS_ORDERS = [99 AtomicOrder.monotonic,100 AtomicOrder.acquire,101 AtomicOrder.release,102 AtomicOrder.acq_rel,103 AtomicOrder.seq_cst,104]105 106CMPXCHG_FAILURE_ORDERS = [107 AtomicOrder.monotonic,108 AtomicOrder.acquire,109 AtomicOrder.seq_cst,110]111 112FENCE_ORDERS = [113 AtomicOrder.acquire,114 AtomicOrder.release,115 AtomicOrder.acq_rel,116 AtomicOrder.seq_cst,117]118 119 120class Feature(enum.Flag):121 # Feature names in filenames are determined by the spelling here:122 v8a = enum.auto()123 v8_1a = enum.auto() # -mattr=+v8.1a, mandatory FEAT_LOR, FEAT_LSE124 rcpc = enum.auto() # FEAT_LRCPC125 lse2 = enum.auto() # FEAT_LSE2126 outline_atomics = enum.auto() # -moutline-atomics127 rcpc3 = enum.auto() # FEAT_LSE2 + FEAT_LRCPC3128 lse2_lse128 = enum.auto() # FEAT_LSE2 + FEAT_LSE128129 130 def test_scope():131 return "all"132 133 @property134 def mattr(self):135 if self == Feature.outline_atomics:136 return "+outline-atomics"137 if self == Feature.v8_1a:138 return "+v8.1a"139 if self == Feature.rcpc3:140 return "+lse2,+rcpc3"141 if self == Feature.lse2_lse128:142 return "+lse2,+lse128"143 return "+" + self.name144 145 146class FPFeature(enum.Flag):147 # Feature names in filenames are determined by the spelling here:148 v8a_fp = enum.auto()149 lsfe = enum.auto() # FEAT_LSFE150 151 def test_scope():152 return "atomicrmw"153 154 @property155 def mattr(self):156 if self == FPFeature.v8a_fp:157 return "+v8a"158 return "+" + self.name159 160 161ATOMICRMW_OPS = [162 "xchg",163 "add",164 "sub",165 "and",166 "nand",167 "or",168 "xor",169 "max",170 "min",171 "umax",172 "umin",173]174 175FP_ATOMICRMW_OPS = [176 "fadd",177 "fsub",178 "fmax",179 "fmin",180 "fmaximum",181 "fminimum",182]183 184 185def relpath():186 # __file__ changed to return absolute path in Python 3.9. Print only187 # up to llvm-project (6 levels higher), to avoid unnecessary diffs and188 # revealing directory structure of people running this script189 top = "../" * 6190 fp = os.path.relpath(__file__, os.path.abspath(os.path.join(__file__, top)))191 return fp192 193 194def generate_unused_res_test(featname, ordering, op, alignval):195 if featname != "lsfe" or op == "fsub" or alignval == 1:196 return False197 if ordering not in [AtomicOrder.monotonic, AtomicOrder.release]:198 return False199 return True200 201 202def align(val, aligned: bool) -> int:203 return val if aligned else 1204 205 206def all_atomicrmw(f, datatype, atomicrmw_ops, featname):207 instr = "atomicrmw"208 generate_unused = False209 tests = []210 for op in atomicrmw_ops:211 for aligned in Aligned:212 for ty, val in datatype:213 alignval = align(val, aligned)214 for ordering in ATOMICRMW_ORDERS:215 name = f"atomicrmw_{op}_{ty}_{aligned}_{ordering}"216 tests.append(217 textwrap.dedent(218 f"""219 define dso_local {ty} @{name}(ptr %ptr, {ty} %value) {{220 %r = {instr} {op} ptr %ptr, {ty} %value {ordering}, align {alignval}221 ret {ty} %r222 }}223 """224 )225 )226 if generate_unused_res_test(featname, ordering, op, alignval):227 generate_unused = True228 name = f"atomicrmw_{op}_{ty}_{aligned}_{ordering}_unused"229 tests.append(230 textwrap.dedent(231 f"""232 define dso_local void @{name}(ptr %ptr, {ty} %value) {{233 %r = {instr} {op} ptr %ptr, {ty} %value {ordering}, align {alignval}234 ret void235 }}236 """237 )238 )239 240 if generate_unused:241 f.write(242 "\n; NOTE: '_unused' tests are added to ensure we do not lower to "243 "ST[F]ADD when the destination register is WZR/XZR.\n"244 "; See discussion on https://github.com/llvm/llvm-project/pull/131174\n"245 )246 247 for test in tests:248 f.write(test)249 250 251def all_load(f):252 for aligned in Aligned:253 for ty, val in Type:254 alignval = align(val, aligned)255 for ordering in ATOMIC_LOAD_ORDERS:256 for const in [False, True]:257 name = f"load_atomic_{ty}_{aligned}_{ordering}"258 instr = "load atomic"259 if const:260 name += "_const"261 arg = "ptr readonly %ptr" if const else "ptr %ptr"262 f.write(263 textwrap.dedent(264 f"""265 define dso_local {ty} @{name}({arg}) {{266 %r = {instr} {ty}, ptr %ptr {ordering}, align {alignval}267 ret {ty} %r268 }}269 """270 )271 )272 273 274def all_store(f):275 for aligned in Aligned:276 for ty, val in Type:277 alignval = align(val, aligned)278 for ordering in ATOMIC_STORE_ORDERS: # FIXME stores279 name = f"store_atomic_{ty}_{aligned}_{ordering}"280 instr = "store atomic"281 f.write(282 textwrap.dedent(283 f"""284 define dso_local void @{name}({ty} %value, ptr %ptr) {{285 {instr} {ty} %value, ptr %ptr {ordering}, align {alignval}286 ret void287 }}288 """289 )290 )291 292 293def all_cmpxchg(f):294 for aligned in Aligned:295 for ty, val in Type:296 alignval = align(val, aligned)297 for success_ordering in CMPXCHG_SUCCESS_ORDERS:298 for failure_ordering in CMPXCHG_FAILURE_ORDERS:299 for weak in [False, True]:300 name = f"cmpxchg_{ty}_{aligned}_{success_ordering}_{failure_ordering}"301 instr = "cmpxchg"302 if weak:303 name += "_weak"304 instr += " weak"305 f.write(306 textwrap.dedent(307 f"""308 define dso_local {ty} @{name}({ty} %expected, {ty} %new, ptr %ptr) {{309 %pair = {instr} ptr %ptr, {ty} %expected, {ty} %new {success_ordering} {failure_ordering}, align {alignval}310 %r = extractvalue {{ {ty}, i1 }} %pair, 0311 ret {ty} %r312 }}313 """314 )315 )316 317 318def all_fence(f):319 for ordering in FENCE_ORDERS:320 name = f"fence_{ordering}"321 f.write(322 textwrap.dedent(323 f"""324 define dso_local void @{name}() {{325 fence {ordering}326 ret void327 }}328 """329 )330 )331 332 333def header(f, triple, features, filter_args: str):334 f.write(335 "; NOTE: Assertions have been autogenerated by "336 "utils/update_llc_test_checks.py UTC_ARGS: "337 )338 f.write(filter_args)339 f.write("\n")340 f.write(f"; The base test file was generated by ./{relpath()}\n")341 342 for feat in features:343 for OptFlag in ["-O0", "-O1"]:344 f.write(345 " ".join(346 [347 ";",348 "RUN:",349 "llc",350 "%s",351 "-o",352 "-",353 "-verify-machineinstrs",354 f"-mtriple={triple}",355 f"-mattr={feat.mattr}",356 OptFlag,357 "|",358 "FileCheck",359 "%s",360 f"--check-prefixes=CHECK,{OptFlag}\n",361 ]362 )363 )364 365 366def write_lit_tests(feature, datatypes, ops):367 for triple in TRIPLES:368 # Feature has no effect on fence, so keep it to one file.369 with open(f"{triple}-fence.ll", "w") as f:370 filter_args = r'--filter "^\s*(dmb)"'371 header(f, triple, Feature, filter_args)372 all_fence(f)373 374 for feat in feature:375 with open(f"{triple}-atomicrmw-{feat.name}.ll", "w") as f:376 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)"'377 header(f, triple, [feat], filter_args)378 all_atomicrmw(f, datatypes, ops, feat.name)379 380 # Floating point atomics only supported for atomicrmw currently381 if feature.test_scope() == "atomicrmw":382 continue383 384 with open(f"{triple}-cmpxchg-{feat.name}.ll", "w") as f:385 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)"'386 header(f, triple, [feat], filter_args)387 all_cmpxchg(f)388 389 with open(f"{triple}-atomic-load-{feat.name}.ll", "w") as f:390 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)"'391 header(f, triple, [feat], filter_args)392 all_load(f)393 394 with open(f"{triple}-atomic-store-{feat.name}.ll", "w") as f:395 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)"'396 header(f, triple, [feat], filter_args)397 all_store(f)398 399 400if __name__ == "__main__":401 os.chdir("llvm/test/CodeGen/AArch64/Atomics/")402 write_lit_tests(Feature, Type, ATOMICRMW_OPS)403 write_lit_tests(FPFeature, FPType, FP_ATOMICRMW_OPS)404 405 print(406 textwrap.dedent(407 """408 Testcases written. To update checks run:409 $ ./llvm/utils/update_llc_test_checks.py -u llvm/test/CodeGen/AArch64/Atomics/*.ll410 411 Or in parallel:412 $ parallel ./llvm/utils/update_llc_test_checks.py -u ::: llvm/test/CodeGen/AArch64/Atomics/*.ll413 """414 )415 )416