testsuite: Add predicate for CPU feature availability
authorBen Gamari <ben@smart-cactus.org>
Thu, 27 Dec 2018 19:41:01 +0000 (14:41 -0500)
committerBen Gamari <ben@smart-cactus.org>
Wed, 20 Feb 2019 19:28:54 +0000 (14:28 -0500)
Previously testing code-generation for ISA extensions was nearly impossible
since we had no ability to determine whether the host supports the needed
extension. Here we fix this by introducing a simple /proc/cpuinfo-based
testsuite predicate. We really ought to

testsuite/driver/cpu_features.py [new file with mode: 0644]
testsuite/driver/runtests.py
testsuite/driver/testlib.py

diff --git a/testsuite/driver/cpu_features.py b/testsuite/driver/cpu_features.py
new file mode 100644 (file)
index 0000000..7716306
--- /dev/null
@@ -0,0 +1,71 @@
+import os
+from testglobals import config
+import subprocess
+import re
+
+# Feature names generally follow the naming used by Linux's /proc/cpuinfo.
+SUPPORTED_CPU_FEATURES = {
+    # These aren't comprehensive; they are only CPU features that we care about
+
+    # x86:
+    'sse', 'sse2', 'sse3', 'ssse3', 'sse4_1', 'sse4_2',
+    'avx1', 'avx2',
+    'popcnt', 'bmi1', 'bmi2'
+}
+
+cpu_feature_cache = None
+
+def get_cpu_features():
+    if config.os in ['mingw32', 'linux'] and os.path.exists('/proc/cpuinfo'):
+        f = open('/proc/cpuinfo').read()
+        flags = re.search(r'flags\s*:\s*.*$', f, re.M)
+        if flags is None:
+            print('get_cpu_features: failed to find cpu features')
+            return {}
+        flags = set(flags.group(0).split())
+        if 'pni' in flags:
+            flags.add('sse3')
+            flags.remove('pni')
+        return flags
+
+    elif config.os == 'darwin':
+        out = subprocess.check_output(['sysctl', 'hw']).decode('UTF-8')
+        features = set()
+        def check_feature(darwin_name, our_name=None):
+            if re.search(r'hw\.optional.%s:\s*1' % darwin_name, out) is not None:
+                features.add(darwin_name if our_name is None else our_name)
+
+        for feature in SUPPORTED_CPU_FEATURES:
+            check_feature(feature)
+
+        # A few annoying cases
+        check_feature('avx1_0', 'avx1')
+        check_feature('avx2_0', 'avx2')
+        return features
+
+    else:
+        # TODO: Add {Open,Free}BSD support
+        print('get_cpu_features: Lacking support for your platform')
+
+    return {}
+
+def have_cpu_feature(feature):
+    """
+    A testsuite predicate for testing the availability of CPU features.
+    """
+    assert feature in SUPPORTED_CPU_FEATURES
+    if cpu_feature_cache is None:
+        cpu_feature_cache = get_cpu_features()
+        print('Found CPU features:', ' '.join(cpu_feature_cache))
+        # Sanity checking
+        assert all(feat in SUPPORTED_CPU_FEATURES
+                   for feat in cpu_feature_cache)
+
+    return feature in cpu_feature_cache
+
+
+if __name__ == '__main__':
+    import sys
+    config.os = sys.argv[1]
+    print(get_cpu_features())
+
index 55b13df..247a5cc 100644 (file)
@@ -27,6 +27,7 @@ from testutil import getStdout, Watcher, str_warn, str_info
 from testglobals import getConfig, ghc_env, getTestRun, TestOptions, brokens
 from perf_notes import MetricChange, inside_git_repo, is_worktree_dirty
 from junit import junit
+import cpu_features
 
 # Readline sometimes spews out ANSI escapes for some values of TERM,
 # which result in test failures. Thus set TERM to a nice, simple, safe
@@ -274,6 +275,12 @@ t.start_time = time.localtime()
 
 print('Beginning test run at', time.strftime("%c %Z",t.start_time))
 
+# For reference
+try:
+    print('Detected CPU features: ', cpu_features.get_cpu_features())
+except Exception as e:
+    print('Failed to detect CPU features: ', e)
+
 sys.stdout.flush()
 # we output text, which cannot be unbuffered
 sys.stdout = os.fdopen(sys.__stdout__.fileno(), "w")
index c09d02a..ba4f6a5 100644 (file)
@@ -20,6 +20,7 @@ import subprocess
 
 from testglobals import config, ghc_env, default_testopts, brokens, t
 from testutil import strip_quotes, lndir, link_or_copy_file, passed, failBecause, str_fail, str_pass
+from cpu_features import have_cpu_feature
 import perf_notes as Perf
 from perf_notes import MetricChange
 extra_src_files = {'T4198': ['exitminus1.c']} # TODO: See #12223