# For manual usage, not as a part of lit tests. Used for generating the following tests:
# cmpxchg-sm30.ll, cmpxchg-sm70.ll, cmpxchg-sm90.ll

from string import Template
from itertools import product

cmpxchg_func = Template(
    """define i$size @${success}_${failure}_i${size}_${addrspace}_${ptx_scope}(ptr${addrspace_cast} %addr, i$size %cmp, i$size %new) {
    %pairold = cmpxchg ptr${addrspace_cast} %addr, i$size %cmp, i$size %new syncscope(\"${llvm_scope}\") $success $failure
    ret i$size %new
}
"""
)

cmpxchg_func_no_scope = Template(
    """define i$size @${success}_${failure}_i${size}_${addrspace}(ptr${addrspace_cast} %addr, i$size %cmp, i$size %new) {
    %pairold = cmpxchg ptr${addrspace_cast} %addr, i$size %cmp, i$size %new $success $failure
    ret i$size %new
}
"""
)

run_statement = Template(
    """; RUN: llc < %s -march=nvptx64 -mcpu=sm_${sm} -mattr=+ptx${ptx} | FileCheck %s --check-prefix=SM${sm}
; RUN: %if ptxas %{ llc < %s -march=nvptx64 -mcpu=sm_${sm} -mattr=+ptx${ptx} | %ptxas-verify -arch=sm_${sm} %}
"""
)


def get_addrspace_cast(addrspace):
    if addrspace == 0:
        return ""
    else:
        return " addrspace({})".format(str(addrspace))


TESTS = [(60, 50), (70, 63), (90, 87)]

LLVM_SCOPES = ["", "block", "cluster", "device"]

SCOPE_LLVM_TO_PTX = {"": "sys", "block": "cta", "cluster": "cluster", "device": "gpu"}

SUCCESS_ORDERINGS = ["monotonic", "acquire", "release", "acq_rel", "seq_cst"]

FAILURE_ORDERINGS = ["monotonic", "acquire", "seq_cst"]

SIZES = [8, 16, 32, 64]

ADDRSPACES = [0, 1, 3]

ADDRSPACE_NUM_TO_ADDRSPACE = {0: "generic", 1: "global", 3: "shared"}


if __name__ == "__main__":
    for sm, ptx in TESTS:
        with open("cmpxchg-sm{}.ll".format(str(sm)), "w") as fp:
            print(run_statement.substitute(sm=sm, ptx=ptx), file=fp)

            # Our test space is: SIZES X SUCCESS_ORDERINGS X FAILURE_ORDERINGS X ADDRSPACES X LLVM_SCOPES
            # This is very large, so we instead test 3 slices.

            # First slice:  are all orderings correctly supported, with and without emulation loops?
            # set addrspace to global, scope to cta, generate all possible orderings, for all operation sizes
            addrspace, llvm_scope = 1, "block"
            for size, success, failure in product(
                SIZES, SUCCESS_ORDERINGS, FAILURE_ORDERINGS
            ):
                print(
                    cmpxchg_func.substitute(
                        success=success,
                        failure=failure,
                        size=size,
                        addrspace=ADDRSPACE_NUM_TO_ADDRSPACE[addrspace],
                        addrspace_cast=get_addrspace_cast(addrspace),
                        llvm_scope=llvm_scope,
                        ptx_scope=SCOPE_LLVM_TO_PTX[llvm_scope],
                    ),
                    file=fp,
                )

            # Second slice: Are all scopes correctlly supported, with and without emulation loops?
            # fix addrspace, ordering, generate all possible scopes, for operation sizes i8, i32
            addrspace, success, failure = 1, "acq_rel", "acquire"
            for size in [8, 32]:
                print(
                    cmpxchg_func_no_scope.substitute(
                        success=success,
                        failure=failure,
                        size=size,
                        addrspace=ADDRSPACE_NUM_TO_ADDRSPACE[addrspace],
                        addrspace_cast=get_addrspace_cast(addrspace),
                    ),
                    file=fp,
                )

            for llvm_scope in LLVM_SCOPES:
                if sm < 90 and llvm_scope == "cluster":
                    continue
                if llvm_scope == "block":
                    # skip (acq_rel, acquire, global, cta)
                    continue
                print(
                    cmpxchg_func.substitute(
                        success=success,
                        failure=failure,
                        size=size,
                        addrspace=ADDRSPACE_NUM_TO_ADDRSPACE[addrspace],
                        addrspace_cast=get_addrspace_cast(addrspace),
                        llvm_scope=llvm_scope,
                        ptx_scope=SCOPE_LLVM_TO_PTX[llvm_scope],
                    ),
                    file=fp,
                )

            # Third slice: Are all address spaces correctly supported?
            # fix ordering, scope, generate all possible address spaces, for operation sizes i8, i32
            success, failure, llvm_scope = "acq_rel", "acquire", "block"
            for size, addrspace in product([8, 32], ADDRSPACES):
                if addrspace == 1:
                    # skip (acq_rel, acquire, global, cta)
                    continue
                print(
                    cmpxchg_func.substitute(
                        success=success,
                        failure=failure,
                        size=size,
                        addrspace=ADDRSPACE_NUM_TO_ADDRSPACE[addrspace],
                        addrspace_cast=get_addrspace_cast(addrspace),
                        llvm_scope=llvm_scope,
                        ptx_scope=SCOPE_LLVM_TO_PTX[llvm_scope],
                    ),
                    file=fp,
                )
