summaryrefslogtreecommitdiff
path: root/numpy/distutils/tests
diff options
context:
space:
mode:
authorSayed Adel <seiko@imavr.com>2020-06-13 18:15:38 +0200
committerSayed Adel <seiko@imavr.com>2020-06-15 22:49:26 +0200
commit8ccd582937a182391f49f412908fecf1770787d7 (patch)
tree330ff615ca7551ac5a9ec1f29b645b5eeb0562a4 /numpy/distutils/tests
parentda21d28ef69e65c5bfef8dc22840fe16fec52540 (diff)
downloadnumpy-8ccd582937a182391f49f412908fecf1770787d7.tar.gz
ENH: [2/7] enable multi-platform SIMD compiler optimizations
Add testing unit for `CCompilerOpt`
Diffstat (limited to 'numpy/distutils/tests')
-rw-r--r--numpy/distutils/tests/test_ccompiler_opt.py787
-rw-r--r--numpy/distutils/tests/test_ccompiler_opt_conf.py169
2 files changed, 956 insertions, 0 deletions
diff --git a/numpy/distutils/tests/test_ccompiler_opt.py b/numpy/distutils/tests/test_ccompiler_opt.py
new file mode 100644
index 000000000..a789be1ea
--- /dev/null
+++ b/numpy/distutils/tests/test_ccompiler_opt.py
@@ -0,0 +1,787 @@
+import re, textwrap, os
+from os import sys, path
+from distutils.errors import DistutilsError
+
+is_standalone = __name__ == '__main__' and __package__ is None
+if is_standalone:
+ import unittest, contextlib, tempfile, shutil
+ sys.path.append(path.abspath(path.join(path.dirname(__file__), "..")))
+ from ccompiler_opt import CCompilerOpt
+
+ # from numpy/testing/_private/utils.py
+ @contextlib.contextmanager
+ def tempdir(*args, **kwargs):
+ tmpdir = tempfile.mkdtemp(*args, **kwargs)
+ try:
+ yield tmpdir
+ finally:
+ shutil.rmtree(tmpdir)
+
+ def assert_(expr, msg=''):
+ if not expr:
+ raise AssertionError(msg)
+else:
+ from numpy.distutils.ccompiler_opt import CCompilerOpt
+ from numpy.testing import assert_, tempdir
+
+# architectures and compilers to test
+arch_compilers = dict(
+ x86 = ("gcc", "clang", "icc", "iccw", "msvc"),
+ x64 = ("gcc", "clang", "icc", "iccw", "msvc"),
+ ppc64 = ("gcc", "clang"),
+ ppc64le = ("gcc", "clang"),
+ armhf = ("gcc", "clang"),
+ aarch64 = ("gcc", "clang"),
+ noarch = ("gcc",)
+)
+
+class FakeCCompilerOpt(CCompilerOpt):
+ fake_info = ""
+ def __init__(self, trap_files="", trap_flags="", *args, **kwargs):
+ self.fake_trap_files = trap_files
+ self.fake_trap_flags = trap_flags
+ CCompilerOpt.__init__(self, None, **kwargs)
+
+ def __repr__(self):
+ return textwrap.dedent("""\
+ <<<<
+ march : {}
+ compiler : {}
+ ----------------
+ {}
+ >>>>
+ """).format(self.cc_march, self.cc_name, self.report())
+
+ def dist_compile(self, sources, flags, **kwargs):
+ assert(isinstance(sources, list))
+ assert(isinstance(flags, list))
+ if self.fake_trap_files:
+ for src in sources:
+ if re.match(self.fake_trap_files, src):
+ self.dist_error("source is trapped by a fake interface")
+ if self.fake_trap_flags:
+ for f in flags:
+ if re.match(self.fake_trap_flags, f):
+ self.dist_error("flag is trapped by a fake interface")
+ # fake objects
+ return zip(sources, [' '.join(flags)] * len(sources))
+
+ def dist_info(self):
+ return FakeCCompilerOpt.fake_info
+
+ @staticmethod
+ def dist_log(*args, stderr=False):
+ pass
+
+class _Test_CCompilerOpt(object):
+ arch = None # x86_64
+ cc = None # gcc
+
+ def setup(self):
+ FakeCCompilerOpt.conf_nocache = True
+ self._opt = None
+
+ def nopt(self, *args, **kwargs):
+ FakeCCompilerOpt.fake_info = self.arch + '_' + self.cc
+ return FakeCCompilerOpt(*args, **kwargs)
+
+ def opt(self):
+ if not self._opt:
+ self._opt = self.nopt()
+ return self._opt
+
+ def march(self):
+ return self.opt().cc_march
+
+ def cc_name(self):
+ return self.opt().cc_name
+
+ def get_targets(self, targets, groups, **kwargs):
+ FakeCCompilerOpt.conf_target_groups = groups
+ opt = self.nopt(
+ cpu_baseline=kwargs.get("baseline", "min"),
+ cpu_dispatch=kwargs.get("dispatch", "max"),
+ trap_files=kwargs.get("trap_files", ""),
+ trap_flags=kwargs.get("trap_flags", "")
+ )
+ with tempdir() as tmpdir:
+ file = os.path.join(tmpdir, "test_targets.c")
+ with open(file, 'w') as f:
+ f.write(targets)
+ gtargets = []
+ gflags = {}
+ fake_objects = opt.try_dispatch([file])
+ for source, flags in fake_objects:
+ gtar = source.split('.')[1:-1]
+ glen = len(gtar)
+ if glen == 0:
+ gtar = "baseline"
+ elif glen == 1:
+ gtar = gtar[0].upper()
+ else:
+ # converting multi-target into parentheses str format to be equivalent
+ # to the configuration statements syntax.
+ gtar = ('('+' '.join(gtar)+')').upper()
+ gtargets.append(gtar)
+ gflags[gtar] = flags
+
+ has_baseline, targets = opt.sources_status[file]
+ targets = targets + ["baseline"] if has_baseline else targets
+ # convert tuple that represent multi-target into parentheses str format
+ targets = [
+ '('+' '.join(tar)+')' if isinstance(tar, tuple) else tar
+ for tar in targets
+ ]
+ if len(targets) != len(gtargets) or not all(t in gtargets for t in targets):
+ raise AssertionError(
+ "'sources_status' returns different targets than the compiled targets\n"
+ "%s != %s" % (targets, gtargets)
+ )
+ # return targets from 'sources_status' since the order is matters
+ return targets, gflags
+
+ def arg_regex(self, **kwargs):
+ map2origin = dict(
+ x64 = "x86",
+ ppc64le = "ppc64",
+ aarch64 = "armhf",
+ clang = "gcc",
+ )
+ march = self.march(); cc_name = self.cc_name()
+ map_march = map2origin.get(march, march)
+ map_cc = map2origin.get(cc_name, cc_name)
+ for key in (
+ march, cc_name, map_march, map_cc,
+ march + '_' + cc_name,
+ map_march + '_' + cc_name,
+ march + '_' + map_cc,
+ map_march + '_' + map_cc,
+ ) :
+ regex = kwargs.pop(key, None)
+ if regex is not None:
+ break
+ if regex:
+ if isinstance(regex, dict):
+ for k, v in regex.items():
+ if v[-1:] not in ')}$?\\.+*':
+ regex[k] = v + '$'
+ else:
+ assert(isinstance(regex, str))
+ if regex[-1:] not in ')}$?\\.+*':
+ regex += '$'
+ return regex
+
+ def expect(self, dispatch, baseline="", **kwargs):
+ match = self.arg_regex(**kwargs)
+ if match is None:
+ return
+ opt = self.nopt(
+ cpu_baseline=baseline, cpu_dispatch=dispatch,
+ trap_files=kwargs.get("trap_files", ""),
+ trap_flags=kwargs.get("trap_flags", "")
+ )
+ features = ' '.join(opt.cpu_dispatch_names())
+ if not match:
+ if len(features) != 0:
+ raise AssertionError(
+ 'expected empty features, not "%s"' % features
+ )
+ return
+ if not re.match(match, features, re.IGNORECASE):
+ raise AssertionError(
+ 'dispatch features "%s" not match "%s"' % (features, match)
+ )
+
+ def expect_baseline(self, baseline, dispatch="", **kwargs):
+ match = self.arg_regex(**kwargs)
+ if match is None:
+ return
+ opt = self.nopt(
+ cpu_baseline=baseline, cpu_dispatch=dispatch,
+ trap_files=kwargs.get("trap_files", ""),
+ trap_flags=kwargs.get("trap_flags", "")
+ )
+ features = ' '.join(opt.cpu_baseline_names())
+ if not match:
+ if len(features) != 0:
+ raise AssertionError(
+ 'expected empty features, not "%s"' % features
+ )
+ return
+ if not re.match(match, features, re.IGNORECASE):
+ raise AssertionError(
+ 'baseline features "%s" not match "%s"' % (features, match)
+ )
+
+ def expect_flags(self, baseline, dispatch="", **kwargs):
+ match = self.arg_regex(**kwargs)
+ if match is None:
+ return
+ opt = self.nopt(
+ cpu_baseline=baseline, cpu_dispatch=dispatch,
+ trap_files=kwargs.get("trap_files", ""),
+ trap_flags=kwargs.get("trap_flags", "")
+ )
+ flags = ' '.join(opt.cpu_baseline_flags())
+ if not match:
+ if len(flags) != 0:
+ raise AssertionError(
+ 'expected empty flags not "%s"' % flags
+ )
+ return
+ if not re.match(match, flags):
+ raise AssertionError(
+ 'flags "%s" not match "%s"' % (flags, match)
+ )
+
+ def expect_targets(self, targets, groups={}, **kwargs):
+ match = self.arg_regex(**kwargs)
+ if match is None:
+ return
+ targets, _ = self.get_targets(targets=targets, groups=groups, **kwargs)
+ targets = ' '.join(targets)
+ if not match:
+ if len(targets) != 0:
+ raise AssertionError(
+ 'expected empty targets, not "%s"' % targets
+ )
+ return
+ if not re.match(match, targets, re.IGNORECASE):
+ raise AssertionError(
+ 'targets "%s" not match "%s"' % (targets, match)
+ )
+
+ def expect_target_flags(self, targets, groups={}, **kwargs):
+ match_dict = self.arg_regex(**kwargs)
+ if match_dict is None:
+ return
+ assert(isinstance(match_dict, dict))
+ _, tar_flags = self.get_targets(targets=targets, groups=groups)
+
+ for match_tar, match_flags in match_dict.items():
+ if match_tar not in tar_flags:
+ raise AssertionError(
+ 'expected to find target "%s"' % match_tar
+ )
+ flags = tar_flags[match_tar]
+ if not match_flags:
+ if len(flags) != 0:
+ raise AssertionError(
+ 'expected to find empty flags in target "%s"' % match_tar
+ )
+ if not re.match(match_flags, flags):
+ raise AssertionError(
+ '"%s" flags "%s" not match "%s"' % (match_tar, flags, match_flags)
+ )
+
+ def test_interface(self):
+ wrong_arch = "ppc64" if self.arch != "ppc64" else "x86"
+ wrong_cc = "clang" if self.cc != "clang" else "icc"
+ opt = self.opt()
+ assert_(getattr(opt, "cc_on_" + self.arch))
+ assert_(not getattr(opt, "cc_on_" + wrong_arch))
+ assert_(getattr(opt, "cc_is_" + self.cc))
+ assert_(not getattr(opt, "cc_is_" + wrong_cc))
+
+ def test_args_empty(self):
+ for baseline, dispatch in (
+ ("", "none"),
+ (None, ""),
+ ("none +none", "none - none"),
+ ("none -max", "min - max"),
+ ("+vsx2 -VSX2", "vsx avx2 avx512f -max"),
+ ("max -vsx - avx + avx512f neon -MAX ",
+ "min -min + max -max -vsx + avx2 -avx2 +NONE")
+ ) :
+ opt = self.nopt(cpu_baseline=baseline, cpu_dispatch=dispatch)
+ assert(len(opt.cpu_baseline_names()) == 0)
+ assert(len(opt.cpu_dispatch_names()) == 0)
+
+ def test_args_validation(self):
+ if self.march() == "unknown":
+ return
+ # check sanity of argument's validation
+ for baseline, dispatch in (
+ ("unkown_feature - max +min", "unknown max min"), # unknowing features
+ ("#avx2", "$vsx") # groups and polices aren't acceptable
+ ) :
+ try:
+ self.nopt(cpu_baseline=baseline, cpu_dispatch=dispatch)
+ raise AssertionError("excepted an exception for invalid arguments")
+ except DistutilsError:
+ pass
+
+ def test_skip(self):
+ # only takes what platform supports and skip the others
+ # without casing exceptions
+ self.expect(
+ "sse vsx neon",
+ x86="sse", ppc64="vsx", armhf="neon", unknown=""
+ )
+ self.expect(
+ "sse41 avx avx2 vsx2 vsx3 neon_vfpv4 asimd",
+ x86 = "sse41 avx avx2",
+ ppc64 = "vsx2 vsx3",
+ armhf = "neon_vfpv4 asimd",
+ unknown = ""
+ )
+ # any features in cpu_dispatch must be ignored if it's part of baseline
+ self.expect(
+ "sse neon vsx", baseline="sse neon vsx",
+ x86="", ppc64="", armhf=""
+ )
+ self.expect(
+ "avx2 vsx3 asimdhp", baseline="avx2 vsx3 asimdhp",
+ x86="", ppc64="", armhf=""
+ )
+
+ def test_implies(self):
+ # baseline combining implied features, so we count
+ # on it instead of testing 'feature_implies()'' directly
+ self.expect_baseline(
+ "fma3 avx2 asimd vsx3",
+ # .* between two spaces can validate features in between
+ x86 = "sse .* sse41 .* fma3.*avx2",
+ ppc64 = "vsx vsx2 vsx3",
+ armhf = "neon neon_fp16 neon_vfpv4 asimd"
+ )
+ """
+ special cases
+ """
+ # in icc and msvc, FMA3 and AVX2 can't be separated
+ # both need to implies each other, same for avx512f & cd
+ for f0, f1 in (
+ ("fma3", "avx2"),
+ ("avx512f", "avx512cd"),
+ ):
+ diff = ".* sse42 .* %s .*%s$" % (f0, f1)
+ self.expect_baseline(f0,
+ x86_gcc=".* sse42 .* %s$" % f0,
+ x86_icc=diff, x86_iccw=diff
+ )
+ self.expect_baseline(f1,
+ x86_gcc=".* avx .* %s$" % f1,
+ x86_icc=diff, x86_iccw=diff
+ )
+ # in msvc, following features can't be separated too
+ for f in (("fma3", "avx2"), ("avx512f", "avx512cd", "avx512_skx")):
+ for ff in f:
+ self.expect_baseline(ff,
+ x86_msvc=".*%s" % ' '.join(f)
+ )
+
+ # in ppc64le VSX and VSX2 can't be separated
+ self.expect_baseline("vsx", ppc64le="vsx vsx2")
+ # in aarch64 following features can't be separated
+ for f in ("neon", "neon_fp16", "neon_vfpv4", "asimd"):
+ self.expect_baseline(f, aarch64="neon neon_fp16 neon_vfpv4 asimd")
+
+ def test_args_options(self):
+ # max & native
+ for o in ("max", "native"):
+ if o == "native" and self.cc_name() == "msvc":
+ continue
+ self.expect(o,
+ trap_files=".*cpu_(sse|vsx|neon).c",
+ x86="", ppc64="", armhf=""
+ )
+ self.expect(o,
+ trap_files=".*cpu_(sse3|vsx2|neon_vfpv4).c",
+ x86="sse sse2", ppc64="vsx", armhf="neon neon_fp16",
+ aarch64="", ppc64le=""
+ )
+ self.expect(o,
+ trap_files=".*cpu_(popcnt|vsx3).c",
+ x86="sse .* sse41", ppc64="vsx vsx2",
+ armhf="neon neon_fp16 .* asimd .*"
+ )
+ self.expect(o,
+ x86_gcc=".* xop fma4 .* avx512f .* avx512_knl avx512_knm avx512_skx .*",
+ # in icc, xop and fam4 aren't supported
+ x86_icc=".* avx512f .* avx512_knl avx512_knm avx512_skx .*",
+ x86_iccw=".* avx512f .* avx512_knl avx512_knm avx512_skx .*",
+ # in msvc, avx512_knl avx512_knm aren't supported
+ x86_msvc=".* xop fma4 .* avx512f .* avx512_skx .*",
+ armhf=".* asimd asimdhp asimddp .*",
+ ppc64="vsx vsx2 vsx3.*"
+ )
+ # min
+ self.expect("min",
+ x86="sse sse2", x64="sse sse2 sse3",
+ armhf="", aarch64="neon neon_fp16 .* asimd",
+ ppc64="", ppc64le="vsx vsx2"
+ )
+ self.expect(
+ "min", trap_files=".*cpu_(sse2|vsx2).c",
+ x86="", ppc64le=""
+ )
+ # an exception must triggered if native flag isn't supported
+ # when option "native" is activated through the args
+ try:
+ self.expect("native",
+ trap_flags=".*(-march=native|-xHost|/QxHost).*",
+ x86=".*", ppc64=".*", armhf=".*"
+ )
+ if self.march() != "unknown":
+ raise AssertionError(
+ "excepted an exception for %s" % self.march()
+ )
+ except DistutilsError:
+ if self.march() == "unknown":
+ raise AssertionError("excepted no exceptions")
+
+ def test_flags(self):
+ self.expect_flags(
+ "sse sse2 vsx vsx2 neon neon_fp16",
+ x86_gcc="-msse -msse2", x86_icc="-msse -msse2",
+ x86_iccw="/arch:SSE2", x86_msvc="/arch:SSE2",
+ ppc64_gcc= "-mcpu=power8",
+ ppc64_clang="-maltivec -mvsx -mpower8-vector",
+ armhf_gcc="-mfpu=neon-fp16 -mfp16-format=ieee",
+ aarch64=""
+ )
+ # testing normalize -march
+ self.expect_flags(
+ "asimd",
+ aarch64="",
+ armhf_gcc=r"-mfp16-format=ieee -mfpu=neon-fp-armv8 -march=armv8-a\+simd"
+ )
+ self.expect_flags(
+ "asimdhp",
+ aarch64_gcc=r"-march=armv8.2-a\+fp16",
+ armhf_gcc=r"-mfp16-format=ieee -mfpu=neon-fp-armv8 -march=armv8.2-a\+fp16"
+ )
+ self.expect_flags(
+ "asimddp", aarch64_gcc=r"-march=armv8.2-a\+dotprod"
+ )
+ self.expect_flags(
+ # asimdfhm implies asimdhp
+ "asimdfhm", aarch64_gcc=r"-march=armv8.2-a\+fp16\+fp16fml"
+ )
+ self.expect_flags(
+ "asimddp asimdhp asimdfhm",
+ aarch64_gcc=r"-march=armv8.2-a\+dotprod\+fp16\+fp16fml"
+ )
+
+ def test_targets_exceptions(self):
+ for targets in (
+ "bla bla", "/*@targets",
+ "/*@targets */",
+ "/*@targets unknown */",
+ "/*@targets $unknown_policy avx2 */",
+ "/*@targets #unknown_group avx2 */",
+ "/*@targets $ */",
+ "/*@targets # vsx */",
+ "/*@targets #$ vsx */",
+ "/*@targets vsx avx2 ) */",
+ "/*@targets vsx avx2 (avx2 */",
+ "/*@targets vsx avx2 () */",
+ "/*@targets vsx avx2 ($autovec) */", # no features
+ "/*@targets vsx avx2 (xxx) */",
+ "/*@targets vsx avx2 (baseline) */",
+ ) :
+ try:
+ self.expect_targets(
+ targets,
+ x86="", armhf="", ppc64=""
+ )
+ if self.march() != "unknown":
+ raise AssertionError(
+ "excepted an exception for %s" % self.march()
+ )
+ except DistutilsError:
+ if self.march() == "unknown":
+ raise AssertionError("excepted no exceptions")
+
+ def test_targets_syntax(self):
+ for targets in (
+ "/*@targets $keep_baseline sse vsx neon*/",
+ "/*@targets,$keep_baseline,sse,vsx,neon*/",
+ "/*@targets*$keep_baseline*sse*vsx*neon*/",
+ """
+ /*
+ ** @targets
+ ** $keep_baseline, sse vsx,neon
+ */
+ """,
+ """
+ /*
+ ************@targets*************
+ ** $keep_baseline, sse vsx, neon
+ *********************************
+ */
+ """,
+ """
+ /*
+ /////////////@targets/////////////////
+ //$keep_baseline//sse//vsx//neon
+ /////////////////////////////////////
+ */
+ """,
+ """
+ /*
+ @targets
+ $keep_baseline
+ SSE VSX NEON*/
+ """
+ ) :
+ self.expect_targets(targets,
+ x86="sse", ppc64="vsx", armhf="neon", unknown=""
+ )
+
+ def test_targets(self):
+ # test skipping baseline features
+ self.expect_targets(
+ """
+ /*@targets
+ sse sse2 sse41 avx avx2 avx512f
+ vsx vsx2 vsx3
+ neon neon_fp16 asimdhp asimddp
+ */
+ """,
+ baseline="avx vsx2 asimd",
+ x86="avx512f avx2", armhf="asimddp asimdhp", ppc64="vsx3"
+ )
+ # test skipping non-dispatch features
+ self.expect_targets(
+ """
+ /*@targets
+ sse41 avx avx2 avx512f
+ vsx2 vsx3
+ asimd asimdhp asimddp
+ */
+ """,
+ baseline="", dispatch="sse41 avx2 vsx2 asimd asimddp",
+ x86="avx2 sse41", armhf="asimddp asimd", ppc64="vsx2"
+ )
+ # test skipping features that not supported
+ self.expect_targets(
+ """
+ /*@targets
+ sse2 sse41 avx2 avx512f
+ vsx2 vsx3
+ neon asimdhp asimddp
+ */
+ """,
+ baseline="",
+ trap_files=".*(avx2|avx512f|vsx3|asimddp).c",
+ x86="sse41 sse2", ppc64="vsx2", armhf="asimdhp neon"
+ )
+ # test skipping features that implies each other
+ self.expect_targets(
+ """
+ /*@targets
+ sse sse2 avx fma3 avx2 avx512f avx512cd
+ vsx vsx2 vsx3
+ neon neon_vfpv4 neon_fp16 neon_fp16 asimd asimdhp
+ asimddp asimdfhm
+ */
+ """,
+ baseline="",
+ x86_gcc="avx512cd avx512f avx2 fma3 avx sse2",
+ x86_msvc="avx512cd avx2 avx sse2",
+ x86_icc="avx512cd avx2 avx sse2",
+ x86_iccw="avx512cd avx2 avx sse2",
+ ppc64="vsx3 vsx2 vsx",
+ ppc64le="vsx3 vsx2",
+ armhf="asimdfhm asimddp asimdhp asimd neon_vfpv4 neon_fp16 neon",
+ aarch64="asimdfhm asimddp asimdhp asimd"
+ )
+
+ def test_targets_policies(self):
+ # 'keep_baseline', generate objects for baseline features
+ self.expect_targets(
+ """
+ /*@targets
+ $keep_baseline
+ sse2 sse42 avx2 avx512f
+ vsx2 vsx3
+ neon neon_vfpv4 asimd asimddp
+ */
+ """,
+ baseline="sse41 avx2 vsx2 asimd vsx3",
+ x86="avx512f avx2 sse42 sse2",
+ ppc64="vsx3 vsx2",
+ armhf="asimddp asimd neon_vfpv4 neon",
+ # neon, neon_vfpv4, asimd implies each other
+ aarch64="asimddp asimd"
+ )
+ # 'keep_sort', leave the sort as-is
+ self.expect_targets(
+ """
+ /*@targets
+ $keep_baseline $keep_sort
+ avx512f sse42 avx2 sse2
+ vsx2 vsx3
+ asimd neon neon_vfpv4 asimddp
+ */
+ """,
+ x86="avx512f sse42 avx2 sse2",
+ ppc64="vsx2 vsx3",
+ armhf="asimd neon neon_vfpv4 asimddp",
+ # neon, neon_vfpv4, asimd implies each other
+ aarch64="asimd asimddp"
+ )
+ # 'autovec', skipping features that can't be
+ # vectorized by the compiler
+ self.expect_targets(
+ """
+ /*@targets
+ $keep_baseline $keep_sort $autovec
+ avx512f avx2 sse42 sse41 sse2
+ vsx3 vsx2
+ asimddp asimd neon_vfpv4 neon
+ */
+ """,
+ x86_gcc="avx512f avx2 sse42 sse41 sse2",
+ x86_icc="avx512f avx2 sse42 sse41 sse2",
+ x86_iccw="avx512f avx2 sse42 sse41 sse2",
+ x86_msvc="avx512f avx2 sse2",
+ ppc64="vsx3 vsx2",
+ armhf="asimddp asimd neon_vfpv4 neon",
+ # neon, neon_vfpv4, asimd implies each other
+ aarch64="asimddp asimd"
+ )
+ for policy in ("$maxopt", "$autovec"):
+ # 'maxopt' and autovec set the max acceptable optimization flags
+ self.expect_target_flags(
+ "/*@targets baseline %s */" % policy,
+ gcc={"baseline":".*-O3.*"}, icc={"baseline":".*-O3.*"},
+ iccw={"baseline":".*/O3.*"}, msvc={"baseline":".*/O2.*"},
+ unknown={"baseline":".*"}
+ )
+
+ # 'werror', force compilers to treat warnings as errors
+ self.expect_target_flags(
+ "/*@targets baseline $werror */",
+ gcc={"baseline":".*-Werror.*"}, icc={"baseline":".*-Werror.*"},
+ iccw={"baseline":".*/Werror.*"}, msvc={"baseline":".*/WX.*"},
+ unknown={"baseline":".*"}
+ )
+
+ def test_targets_groups(self):
+ self.expect_targets(
+ """
+ /*@targets $keep_baseline baseline #test_group */
+ """,
+ groups=dict(
+ test_group=("""
+ $keep_baseline
+ asimddp sse2 vsx2 avx2 vsx3
+ avx512f asimdhp
+ """)
+ ),
+ x86="avx512f avx2 sse2 baseline",
+ ppc64="vsx3 vsx2 baseline",
+ armhf="asimddp asimdhp baseline"
+ )
+ # test skip duplicating and sorting
+ self.expect_targets(
+ """
+ /*@targets
+ * sse42 avx avx512f
+ * #test_group_1
+ * vsx2
+ * #test_group_2
+ * asimddp asimdfhm
+ */
+ """,
+ groups=dict(
+ test_group_1=("""
+ VSX2 vsx3 asimd avx2 SSE41
+ """),
+ test_group_2=("""
+ vsx2 vsx3 asImd aVx2 sse41
+ """)
+ ),
+ x86="avx512f avx2 avx sse42 sse41",
+ ppc64="vsx3 vsx2",
+ # vsx2 part of the default baseline of ppc64le, option ("min")
+ ppc64le="vsx3",
+ armhf="asimdfhm asimddp asimd",
+ # asimd part of the default baseline of aarch64, option ("min")
+ aarch64="asimdfhm asimddp"
+ )
+
+ def test_targets_multi(self):
+ self.expect_targets(
+ """
+ /*@targets
+ (avx512_clx avx512_cnl) (asimdhp asimddp)
+ */
+ """,
+ x86=r"\(avx512_clx avx512_cnl\)",
+ armhf=r"\(asimdhp asimddp\)",
+ )
+ # test skipping implied features and auto-sort
+ self.expect_targets(
+ """
+ /*@targets
+ f16c (sse41 avx sse42) (sse3 avx2 avx512f)
+ vsx2 (vsx vsx3 vsx2)
+ (neon neon_vfpv4 asimd asimdhp asimddp)
+ */
+ """,
+ x86="avx512f f16c avx",
+ ppc64="vsx3 vsx2",
+ ppc64le="vsx3", # vsx2 part of baseline
+ armhf=r"\(asimdhp asimddp\)",
+ )
+ # test skipping implied features and keep sort
+ self.expect_targets(
+ """
+ /*@targets $keep_sort
+ (sse41 avx sse42) (sse3 avx2 avx512f)
+ (vsx vsx3 vsx2)
+ (asimddp neon neon_vfpv4 asimd asimdhp)
+ */
+ """,
+ x86="avx avx512f",
+ ppc64="vsx3",
+ armhf=r"\(asimdhp asimddp\)",
+ )
+ # test compiler variety and avoiding duplicating
+ self.expect_targets(
+ """
+ /*@targets $keep_sort
+ fma3 avx2 (fma3 avx2) (avx2 fma3) avx2 fma3
+ */
+ """,
+ x86_gcc=r"fma3 avx2 \(fma3 avx2\)",
+ x86_icc="avx2", x86_iccw="avx2",
+ x86_msvc="avx2"
+ )
+
+def new_test(arch, cc):
+ if is_standalone: return textwrap.dedent("""\
+ class TestCCompilerOpt_{class_name}(_Test_CCompilerOpt, unittest.TestCase):
+ arch = '{arch}'
+ cc = '{cc}'
+ def __init__(self, methodName="runTest"):
+ unittest.TestCase.__init__(self, methodName)
+ self.setup()
+ """).format(
+ class_name=arch + '_' + cc, arch=arch, cc=cc
+ )
+ return textwrap.dedent("""\
+ class TestCCompilerOpt_{class_name}(_Test_CCompilerOpt):
+ arch = '{arch}'
+ cc = '{cc}'
+ """).format(
+ class_name=arch + '_' + cc, arch=arch, cc=cc
+ )
+"""
+if 1 and is_standalone:
+ FakeCCompilerOpt.fake_info = "x86_icc"
+ cco = FakeCCompilerOpt(None, cpu_baseline="avx2")
+ print(' '.join(cco.cpu_baseline_names()))
+ print(cco.cpu_baseline_flags())
+ unittest.main()
+ sys.exit()
+"""
+for arch, compilers in arch_compilers.items():
+ for cc in compilers:
+ exec(new_test(arch, cc))
+
+if is_standalone:
+ unittest.main()
diff --git a/numpy/distutils/tests/test_ccompiler_opt_conf.py b/numpy/distutils/tests/test_ccompiler_opt_conf.py
new file mode 100644
index 000000000..2f83a59e0
--- /dev/null
+++ b/numpy/distutils/tests/test_ccompiler_opt_conf.py
@@ -0,0 +1,169 @@
+import unittest
+from os import sys, path
+
+is_standalone = __name__ == '__main__' and __package__ is None
+if is_standalone:
+ sys.path.append(path.abspath(path.join(path.dirname(__file__), "..")))
+ from ccompiler_opt import CCompilerOpt
+else:
+ from numpy.distutils.ccompiler_opt import CCompilerOpt
+
+arch_compilers = dict(
+ x86 = ("gcc", "clang", "icc", "iccw", "msvc"),
+ x64 = ("gcc", "clang", "icc", "iccw", "msvc"),
+ ppc64 = ("gcc", "clang"),
+ ppc64le = ("gcc", "clang"),
+ armhf = ("gcc", "clang"),
+ aarch64 = ("gcc", "clang"),
+ narch = ("gcc",)
+)
+
+class FakeCCompilerOpt(CCompilerOpt):
+ fake_info = ""
+ def __init__(self, *args, **kwargs):
+ CCompilerOpt.__init__(self, None, **kwargs)
+ def dist_compile(self, sources, flags, **kwargs):
+ return sources
+ def dist_info(self):
+ return FakeCCompilerOpt.fake_info
+ @staticmethod
+ def dist_log(*args, stderr=False):
+ pass
+
+class _TestConfFeatures(FakeCCompilerOpt):
+ """A hook to check the sanity of configured features
+- before it called by the abstract class '_Feature'
+ """
+
+ def conf_features_partial(self):
+ conf_all = self.conf_features
+ for feature_name, feature in conf_all.items():
+ self.test_feature(
+ "attribute conf_features",
+ conf_all, feature_name, feature
+ )
+
+ conf_partial = FakeCCompilerOpt.conf_features_partial(self)
+ for feature_name, feature in conf_partial.items():
+ self.test_feature(
+ "conf_features_partial()",
+ conf_partial, feature_name, feature
+ )
+ return conf_partial
+
+ def test_feature(self, log, search_in, feature_name, feature_dict):
+ error_msg = (
+ "during validate '{}' within feature '{}', "
+ "march '{}' and compiler '{}'\n>> "
+ ).format(log, feature_name, self.cc_march, self.cc_name)
+
+ if not feature_name.isupper():
+ raise AssertionError(error_msg + "feature name must be in uppercase")
+
+ for option, val in feature_dict.items():
+ self.test_option_types(error_msg, option, val)
+ self.test_duplicates(error_msg, option, val)
+
+ self.test_implies(error_msg, search_in, feature_name, feature_dict)
+ self.test_group(error_msg, search_in, feature_name, feature_dict)
+
+ def test_option_types(self, error_msg, option, val):
+ for tp, available in (
+ ((str, list), (
+ "implies", "headers", "flags", "group", "detect"
+ )),
+ ((str,), ("disable",)),
+ ((int,), ("interest",)),
+ ((bool,), ("implies_detect",)),
+ ((bool, type(None)), ("autovec",)),
+ ) :
+ found_it = option in available
+ if not found_it:
+ continue
+ if not isinstance(val, tp):
+ error_tp = [t.__name__ for t in (*tp,)]
+ error_tp = ' or '.join(error_tp)
+ raise AssertionError(error_msg + \
+ "expected '%s' type for option '%s' not '%s'" % (
+ error_tp, option, type(val).__name__
+ ))
+ break
+
+ if not found_it:
+ raise AssertionError(error_msg + \
+ "invalid option name '%s'" % option
+ )
+
+ def test_duplicates(self, error_msg, option, val):
+ if option not in (
+ "implies", "headers", "flags", "group", "detect"
+ ) : return
+
+ if isinstance(val, str):
+ val = val.split()
+
+ if len(val) != len(set(val)):
+ raise AssertionError(error_msg + \
+ "duplicated values in option '%s'" % option
+ )
+
+ def test_implies(self, error_msg, search_in, feature_name, feature_dict):
+ if feature_dict.get("disabled") is not None:
+ return
+ implies = feature_dict.get("implies", "")
+ if not implies:
+ return
+ if isinstance(implies, str):
+ implies = implies.split()
+
+ if feature_name in implies:
+ raise AssertionError(error_msg + \
+ "feature implies itself"
+ )
+
+ for impl in implies:
+ impl_dict = search_in.get(impl)
+ if impl_dict is not None:
+ if "disable" in impl_dict:
+ raise AssertionError(error_msg + \
+ "implies disabled feature '%s'" % impl
+ )
+ continue
+ raise AssertionError(error_msg + \
+ "implies non-exist feature '%s'" % impl
+ )
+
+ def test_group(self, error_msg, search_in, feature_name, feature_dict):
+ if feature_dict.get("disabled") is not None:
+ return
+ group = feature_dict.get("group", "")
+ if not group:
+ return
+ if isinstance(group, str):
+ group = group.split()
+
+ for f in group:
+ impl_dict = search_in.get(f)
+ if not impl_dict or "disable" in impl_dict:
+ continue
+ raise AssertionError(error_msg + \
+ "in option '%s', '%s' already exists as a feature name" % (
+ option, f
+ ))
+
+class TestConfFeatures(unittest.TestCase):
+ def __init__(self, methodName="runTest"):
+ unittest.TestCase.__init__(self, methodName)
+ self.setup()
+
+ def setup(self):
+ FakeCCompilerOpt.conf_nocache = True
+
+ def test_features(self):
+ for arch, compilers in arch_compilers.items():
+ for cc in compilers:
+ FakeCCompilerOpt.fake_info = arch + cc
+ _TestConfFeatures()
+
+if is_standalone:
+ unittest.main()