diff --git a/bashi/filter_compiler_version.py b/bashi/filter_compiler_version.py index 9cb8b41..7615a31 100644 --- a/bashi/filter_compiler_version.py +++ b/bashi/filter_compiler_version.py @@ -11,6 +11,7 @@ from typeguard import typechecked from bashi.globals import * # pylint: disable=wildcard-import,unused-wildcard-import from bashi.types import Parameter, ParameterValueTuple +from bashi.versions import NVCC_GCC_MAX_VERSION from bashi.utils import reason @@ -60,4 +61,27 @@ def compiler_version_filter( reason(output, "host and device compiler version must be the same (except for nvcc)") return False + # now idea, how remove nested blocks without hitting the performance + # pylint: disable=too-many-nested-blocks + if DEVICE_COMPILER in row and row[DEVICE_COMPILER].name == NVCC: + if HOST_COMPILER in row and row[HOST_COMPILER].name == GCC: + # Rule: v2 + # remove all unsupported nvcc gcc version combinations + # define which is the latest supported gcc compiler for a nvcc version + + # if a nvcc version is not supported by bashi, assume that the version supports the + # latest gcc compiler version + if row[DEVICE_COMPILER].version <= NVCC_GCC_MAX_VERSION[0].nvcc: + # check the maximum supported gcc version for the given nvcc version + for comb in NVCC_GCC_MAX_VERSION: + if row[DEVICE_COMPILER].version >= comb.nvcc: + if row[HOST_COMPILER].version > comb.host: + reason( + output, + f"nvcc {row[DEVICE_COMPILER].version} " + f"does not support gcc {row[HOST_COMPILER].version}", + ) + return False + break + return True diff --git a/bashi/utils.py b/bashi/utils.py index c9ace4e..6a0b1aa 100644 --- a/bashi/utils.py +++ b/bashi/utils.py @@ -18,7 +18,7 @@ ParameterValueSingle, ParameterValueTuple, ) -from bashi.versions import COMPILERS, VERSIONS +from bashi.versions import COMPILERS, VERSIONS, NVCC_GCC_MAX_VERSION from bashi.globals import * # pylint: disable=wildcard-import,unused-wildcard-import @@ -316,6 +316,7 @@ def reason(output: Optional[IO[str]], msg: str): ) +# pylint: disable=too-many-branches @typechecked def get_expected_bashi_parameter_value_pairs( parameter_matrix: ParameterValueMatrix, @@ -383,4 +384,22 @@ def get_expected_bashi_parameter_value_pairs( all_versions=False, ) + # remove all gcc version, which are to new for a specific nvcc version + nvcc_versions = [packaging.version.parse(str(v)) for v in VERSIONS[NVCC]] + nvcc_versions.sort() + gcc_versions = [packaging.version.parse(str(v)) for v in VERSIONS[GCC]] + gcc_versions.sort() + for nvcc_version in nvcc_versions: + for max_nvcc_gcc_version in NVCC_GCC_MAX_VERSION: + if nvcc_version >= max_nvcc_gcc_version.nvcc: + for gcc_version in gcc_versions: + if gcc_version > max_nvcc_gcc_version.host: + remove_parameter_value_pair( + to_remove=create_parameter_value_pair( + HOST_COMPILER, GCC, gcc_version, DEVICE_COMPILER, NVCC, nvcc_version + ), + parameter_value_pairs=param_val_pair_list, + ) + break + return param_val_pair_list diff --git a/bashi/versions.py b/bashi/versions.py index 1343d7f..5d14622 100644 --- a/bashi/versions.py +++ b/bashi/versions.py @@ -7,6 +7,30 @@ from bashi.globals import * # pylint: disable=wildcard-import,unused-wildcard-import from bashi.types import ValueName, ValueVersion, ParameterValue, ParameterValueMatrix + +class NvccHostSupport: + """Contains a nvcc version and host compiler version. Does automatically parse the input strings + to package.version.Version. + + Provides comparision operators for sorting. + """ + + def __init__(self, nvcc_version: str, host_version: str): + self.nvcc = pkv.parse(nvcc_version) + self.host = pkv.parse(host_version) + + def __lt__(self, other: "NvccHostSupport") -> bool: + return self.nvcc < other.nvcc + + def __eq__(self, other: object) -> bool: + if not isinstance(other, NvccHostSupport): + raise TypeError("does not support other types than NvccHostSupport") + return self.nvcc == other.nvcc and self.host == other.host + + def __str__(self) -> str: + return f"nvcc {str(self.nvcc)} + host version {self.host}" + + VERSIONS: Dict[str, List[Union[str, int, float]]] = { GCC: [6, 7, 8, 9, 10, 11, 12, 13], CLANG: [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17], @@ -26,6 +50,7 @@ 12.0, 12.1, 12.2, + 12.3, ], HIPCC: [5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 6.0], ICPX: ["2023.1.0", "2023.2.0"], @@ -45,6 +70,24 @@ CXX_STANDARD: [17, 20], } +# define the maximum supported gcc version for a specific nvcc version +# the latest supported nvcc version must be added, even if the supported gcc version does not +# increase +# e.g.: +# NvccHostSupport("12.3", "12"), +# NvccHostSupport("12.0", "12"), +# NvccHostSupport("11.4", "11"), +NVCC_GCC_MAX_VERSION: List[NvccHostSupport] = [ + NvccHostSupport("12.3", "12"), + NvccHostSupport("12.0", "12"), + NvccHostSupport("11.4", "11"), + NvccHostSupport("11.1", "10"), + NvccHostSupport("11.0", "9"), + NvccHostSupport("10.1", "8"), + NvccHostSupport("10.0", "7"), +] +NVCC_GCC_MAX_VERSION.sort(reverse=True) + def get_parameter_value_matrix() -> ParameterValueMatrix: """Generates a parameter-value-matrix from all supported compilers, softwares and compilation diff --git a/tests/test_nvcc_filter.py b/tests/test_nvcc_filter.py index e675e40..125c4e9 100644 --- a/tests/test_nvcc_filter.py +++ b/tests/test_nvcc_filter.py @@ -5,7 +5,9 @@ from collections import OrderedDict as OD from utils_test import parse_param_val as ppv from bashi.globals import * # pylint: disable=wildcard-import,unused-wildcard-import +from bashi.versions import VERSIONS, NvccHostSupport from bashi.filter_compiler_name import compiler_name_filter_typechecked +from bashi.filter_compiler_version import compiler_version_filter_typechecked class TestNoNvccHostCompiler(unittest.TestCase): @@ -185,3 +187,320 @@ def test_valid_combination_rule_n2(self): ) ) ) + + +class TestNvccHostSupportClass(unittest.TestCase): + def test_sorting(self): + data1: List[NvccHostSupport] = [ + NvccHostSupport("11.8", "12"), + NvccHostSupport("10.1", "10"), + NvccHostSupport("12.3", "13"), + NvccHostSupport("11.2", "14"), + NvccHostSupport("11.4", "10"), + NvccHostSupport("11.5", "12"), + NvccHostSupport("11.3", "9"), + ] + + expect_data1: List[NvccHostSupport] = [ + NvccHostSupport("10.1", "10"), + NvccHostSupport("11.2", "14"), + NvccHostSupport("11.3", "9"), + NvccHostSupport("11.4", "10"), + NvccHostSupport("11.5", "12"), + NvccHostSupport("11.8", "12"), + NvccHostSupport("12.3", "13"), + ] + + data1.sort() + + self.assertEqual( + data1, + expect_data1, + f"\ngiven : {[str(x) for x in data1]}\n" + f"expected: {[str(x) for x in expect_data1]}", + ) + + # the comparision operator does not respect the host version + data2: List[NvccHostSupport] = [ + NvccHostSupport("11.8", "12"), + NvccHostSupport("12.3", "13"), + NvccHostSupport("12.3", "12"), + NvccHostSupport("11.2", "14"), + ] + + expect_data2: List[NvccHostSupport] = [ + NvccHostSupport("11.2", "14"), + NvccHostSupport("11.8", "12"), + NvccHostSupport("12.3", "13"), + NvccHostSupport("12.3", "12"), + ] + + data2.sort() + + self.assertEqual( + data2, + expect_data2, + f"\ngiven : {[str(x) for x in data2]}\n" + f"expected: {[str(x) for x in expect_data2]}", + ) + + data3: List[NvccHostSupport] = [ + NvccHostSupport("11.8", "12"), + NvccHostSupport("10.1", "10"), + NvccHostSupport("12.3", "13"), + NvccHostSupport("11.2", "14"), + NvccHostSupport("11.4", "10"), + NvccHostSupport("11.5", "12"), + NvccHostSupport("11.3", "9"), + ] + + expect_data3: List[NvccHostSupport] = [ + NvccHostSupport("12.3", "13"), + NvccHostSupport("11.8", "12"), + NvccHostSupport("11.5", "12"), + NvccHostSupport("11.4", "10"), + NvccHostSupport("11.3", "9"), + NvccHostSupport("11.2", "14"), + NvccHostSupport("10.1", "10"), + ] + + data3.sort(reverse=True) + + self.assertEqual( + data3, + expect_data3, + f"\ngiven : {[str(x) for x in data3]}\n" + f"expected: {[str(x) for x in expect_data3]}", + ) + + +class TestNvccSupportedGccVersion(unittest.TestCase): + def test_valid_combination_general_algorithm_rule_v2(self): + # this tests checks, if also version are respected, which are located between two defined + # nvcc versions + # e.g the following was defined: + # [ ..., NvccHostSupport("12.0", "12"), NvccHostSupport("11.4", "11"), ...] + # nvcc 11.5 should also pass with gcc 11 and not pass with gcc 12 + expected_results = [ + ("10.0", "6", True), + ("10.0", "7", True), + ("10.0", "8", False), + ("10.0", "13", False), + ("10.1", "6", True), + ("10.1", "7", True), + ("10.1", "8", True), + ("10.1", "13", False), + ("10.2", "6", True), + ("10.2", "7", True), + ("10.2", "8", True), + ("10.2", "13", False), + ("11.0", "6", True), + ("11.0", "9", True), + ("11.0", "10", False), + ("11.0", "12", False), + ("11.3", "8", True), + ("11.3", "9", True), + ("11.3", "10", True), + ("11.3", "11", False), + ("11.3", "12", False), + ("11.4", "8", True), + ("11.4", "9", True), + ("11.4", "10", True), + ("11.4", "11", True), + ("11.4", "12", False), + ("11.5", "8", True), + ("11.5", "9", True), + ("11.5", "10", True), + ("11.5", "11", True), + ("11.5", "12", False), + ("11.6", "8", True), + ("11.6", "9", True), + ("11.6", "10", True), + ("11.6", "11", True), + ("11.6", "12", False), + ] + + for nvcc_version, gcc_version, expected_filter_return_value in expected_results: + reason_msg = io.StringIO() + self.assertEqual( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, gcc_version)), + DEVICE_COMPILER: ppv((NVCC, nvcc_version)), + } + ), + reason_msg, + ), + expected_filter_return_value, + f"the filter for the combination of nvcc {nvcc_version} + gcc {gcc_version} " + f"should return {expected_filter_return_value}", + ) + if not expected_filter_return_value: + self.assertEqual( + reason_msg.getvalue(), + f"nvcc {nvcc_version} " f"does not support gcc {gcc_version}", + ) + + def test_valid_combination_max_gcc_rule_v2(self): + # change the version, if you added a new cuda release + # this test is a guard to be sure, that the following test contains the latest nvcc release + latest_covered_nvcc_release = "12.3" + self.assertEqual( + latest_covered_nvcc_release, + str(VERSIONS[NVCC][-1]), + f"The tests cases covers up to nvcc version {latest_covered_nvcc_release}.\n" + f"VERSION[NVCC] defines nvcc {VERSIONS[NVCC][-1]} as latest supported version.", + ) + + # add the latest supported gcc version for a supported nvcc version and also the successor + # gcc version + expected_results = [ + ("10.0", "7", True), + ("10.0", "8", False), + ("10.1", "8", True), + ("10.1", "9", False), + ("10.2", "8", True), + ("10.2", "9", False), + ("11.0", "9", True), + ("11.0", "10", False), + ("11.1", "10", True), + ("11.1", "11", False), + ("11.2", "10", True), + ("11.2", "11", False), + ("11.3", "10", True), + ("11.3", "11", False), + ("11.4", "11", True), + ("11.4", "12", False), + ("11.5", "11", True), + ("11.5", "12", False), + ("11.6", "11", True), + ("11.6", "12", False), + ("11.7", "11", True), + ("11.7", "12", False), + ("11.8", "11", True), + ("11.8", "12", False), + ("12.0", "12", True), + ("12.0", "13", False), + ("12.1", "12", True), + ("12.1", "13", False), + ("12.2", "12", True), + ("12.2", "13", False), + ("12.3", "12", True), + ("12.3", "13", False), + ] + + for nvcc_version, gcc_version, expected_filter_return_value in expected_results: + reason_msg = io.StringIO() + self.assertEqual( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, gcc_version)), + DEVICE_COMPILER: ppv((NVCC, nvcc_version)), + } + ), + reason_msg, + ), + expected_filter_return_value, + f"the filter for the combination of nvcc {nvcc_version} + gcc {gcc_version} " + f"should return {expected_filter_return_value}", + ) + if not expected_filter_return_value: + self.assertEqual( + reason_msg.getvalue(), + f"nvcc {nvcc_version} " f"does not support gcc {gcc_version}", + ) + + def test_valid_multi_row_entries_gcc_rule_v2(self): + self.assertTrue( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, 10)), + DEVICE_COMPILER: ppv((NVCC, 11.2)), + CMAKE: ppv((CMAKE, 3.18)), + BOOST: ppv((BOOST, 1.78)), + } + ) + ) + ) + + self.assertTrue( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, 12)), + ALPAKA_ACC_GPU_CUDA_ENABLE: ppv((ALPAKA_ACC_GPU_CUDA_ENABLE, 12.1)), + DEVICE_COMPILER: ppv((NVCC, 12.1)), + ALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLE: ppv( + (ALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLE, "1.0.0") + ), + } + ) + ) + ) + + def test_invalid_multi_row_entries_gcc_rule_v2(self): + reason_msg1 = io.StringIO() + self.assertFalse( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, 13)), + DEVICE_COMPILER: ppv((NVCC, 11.2)), + CMAKE: ppv((CMAKE, 3.18)), + BOOST: ppv((BOOST, 1.78)), + } + ), + reason_msg1, + ), + ) + self.assertEqual( + reason_msg1.getvalue(), + "nvcc 11.2 does not support gcc 13", + ) + + reason_msg2 = io.StringIO() + self.assertFalse( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, 12)), + ALPAKA_ACC_GPU_CUDA_ENABLE: ppv((ALPAKA_ACC_GPU_CUDA_ENABLE, 11.8)), + DEVICE_COMPILER: ppv((NVCC, 11.8)), + ALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLE: ppv( + (ALPAKA_ACC_CPU_B_SEQ_T_SEQ_ENABLE, "1.0.0") + ), + } + ), + reason_msg2, + ) + ) + self.assertEqual( + reason_msg2.getvalue(), + "nvcc 11.8 does not support gcc 12", + ) + + def test_unknown_combination_gcc_rule_v2(self): + # test an unsupported nvcc version + # we assume, that the nvcc supports all gcc versions + unsupported_nvcc_version = 42.0 + self.assertFalse( + unsupported_nvcc_version in VERSIONS[NVCC], + f"for the test, it is required that nvcc {unsupported_nvcc_version} is an unsupported " + "version", + ) + + self.assertTrue( + compiler_version_filter_typechecked( + OD( + { + HOST_COMPILER: ppv((GCC, 12)), + DEVICE_COMPILER: ppv((NVCC, unsupported_nvcc_version)), + } + ), + ), + f"nvcc {unsupported_nvcc_version} should pass the filter, because it is unknown " + "version", + )