summaryrefslogtreecommitdiff
path: root/lib/spack/external/archspec/cpu/detect.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/spack/external/archspec/cpu/detect.py')
-rw-r--r--lib/spack/external/archspec/cpu/detect.py270
1 files changed, 270 insertions, 0 deletions
diff --git a/lib/spack/external/archspec/cpu/detect.py b/lib/spack/external/archspec/cpu/detect.py
new file mode 100644
index 0000000000..e65076c788
--- /dev/null
+++ b/lib/spack/external/archspec/cpu/detect.py
@@ -0,0 +1,270 @@
+# Copyright 2019-2020 Lawrence Livermore National Security, LLC and other
+# Archspec Project Developers. See the top-level COPYRIGHT file for details.
+#
+# SPDX-License-Identifier: (Apache-2.0 OR MIT)
+"""Detection of CPU microarchitectures"""
+import collections
+import functools
+import os
+import platform
+import re
+import subprocess
+import warnings
+
+import six
+
+from .microarchitecture import generic_microarchitecture, TARGETS
+from .schema import TARGETS_JSON
+
+#: Mapping from operating systems to chain of commands
+#: to obtain a dictionary of raw info on the current cpu
+INFO_FACTORY = collections.defaultdict(list)
+
+#: Mapping from micro-architecture families (x86_64, ppc64le, etc.) to
+#: functions checking the compatibility of the host with a given target
+COMPATIBILITY_CHECKS = {}
+
+
+def info_dict(operating_system):
+ """Decorator to mark functions that are meant to return raw info on
+ the current cpu.
+
+ Args:
+ operating_system (str or tuple): operating system for which the marked
+ function is a viable factory of raw info dictionaries.
+ """
+
+ def decorator(factory):
+ INFO_FACTORY[operating_system].append(factory)
+
+ @functools.wraps(factory)
+ def _impl():
+ info = factory()
+
+ # Check that info contains a few mandatory fields
+ msg = 'field "{0}" is missing from raw info dictionary'
+ assert "vendor_id" in info, msg.format("vendor_id")
+ assert "flags" in info, msg.format("flags")
+ assert "model" in info, msg.format("model")
+ assert "model_name" in info, msg.format("model_name")
+
+ return info
+
+ return _impl
+
+ return decorator
+
+
+@info_dict(operating_system="Linux")
+def proc_cpuinfo():
+ """Returns a raw info dictionary by parsing the first entry of
+ ``/proc/cpuinfo``
+ """
+ info = {}
+ with open("/proc/cpuinfo") as file:
+ for line in file:
+ key, separator, value = line.partition(":")
+
+ # If there's no separator and info was already populated
+ # according to what's written here:
+ #
+ # http://www.linfo.org/proc_cpuinfo.html
+ #
+ # we are on a blank line separating two cpus. Exit early as
+ # we want to read just the first entry in /proc/cpuinfo
+ if separator != ":" and info:
+ break
+
+ info[key.strip()] = value.strip()
+ return info
+
+
+def _check_output(args, env):
+ output = subprocess.Popen(args, stdout=subprocess.PIPE, env=env).communicate()[0]
+ return six.text_type(output.decode("utf-8"))
+
+
+@info_dict(operating_system="Darwin")
+def sysctl_info_dict():
+ """Returns a raw info dictionary parsing the output of sysctl."""
+ # Make sure that /sbin and /usr/sbin are in PATH as sysctl is
+ # usually found there
+ child_environment = dict(os.environ.items())
+ search_paths = child_environment.get("PATH", "").split(os.pathsep)
+ for additional_path in ("/sbin", "/usr/sbin"):
+ if additional_path not in search_paths:
+ search_paths.append(additional_path)
+ child_environment["PATH"] = os.pathsep.join(search_paths)
+
+ def sysctl(*args):
+ return _check_output(["sysctl"] + list(args), env=child_environment).strip()
+
+ flags = (
+ sysctl("-n", "machdep.cpu.features").lower()
+ + " "
+ + sysctl("-n", "machdep.cpu.leaf7_features").lower()
+ )
+ info = {
+ "vendor_id": sysctl("-n", "machdep.cpu.vendor"),
+ "flags": flags,
+ "model": sysctl("-n", "machdep.cpu.model"),
+ "model name": sysctl("-n", "machdep.cpu.brand_string"),
+ }
+ return info
+
+
+def adjust_raw_flags(info):
+ """Adjust the flags detected on the system to homogenize
+ slightly different representations.
+ """
+ # Flags detected on Darwin turned to their linux counterpart
+ flags = info.get("flags", [])
+ d2l = TARGETS_JSON["conversions"]["darwin_flags"]
+ for darwin_flag, linux_flag in d2l.items():
+ if darwin_flag in flags:
+ info["flags"] += " " + linux_flag
+
+
+def adjust_raw_vendor(info):
+ """Adjust the vendor field to make it human readable"""
+ if "CPU implementer" not in info:
+ return
+
+ # Mapping numeric codes to vendor (ARM). This list is a merge from
+ # different sources:
+ #
+ # https://github.com/karelzak/util-linux/blob/master/sys-utils/lscpu-arm.c
+ # https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile
+ # https://github.com/gcc-mirror/gcc/blob/master/gcc/config/aarch64/aarch64-cores.def
+ # https://patchwork.kernel.org/patch/10524949/
+ arm_vendors = TARGETS_JSON["conversions"]["arm_vendors"]
+ arm_code = info["CPU implementer"]
+ if arm_code in arm_vendors:
+ info["CPU implementer"] = arm_vendors[arm_code]
+
+
+def raw_info_dictionary():
+ """Returns a dictionary with information on the cpu of the current host.
+
+ This function calls all the viable factories one after the other until
+ there's one that is able to produce the requested information.
+ """
+ # pylint: disable=broad-except
+ info = {}
+ for factory in INFO_FACTORY[platform.system()]:
+ try:
+ info = factory()
+ except Exception as exc:
+ warnings.warn(str(exc))
+
+ if info:
+ adjust_raw_flags(info)
+ adjust_raw_vendor(info)
+ break
+
+ return info
+
+
+def compatible_microarchitectures(info):
+ """Returns an unordered list of known micro-architectures that are
+ compatible with the info dictionary passed as argument.
+
+ Args:
+ info (dict): dictionary containing information on the host cpu
+ """
+ architecture_family = platform.machine()
+ # If a tester is not registered, be conservative and assume no known
+ # target is compatible with the host
+ tester = COMPATIBILITY_CHECKS.get(architecture_family, lambda x, y: False)
+ return [x for x in TARGETS.values() if tester(info, x)] or [
+ generic_microarchitecture(architecture_family)
+ ]
+
+
+def host():
+ """Detects the host micro-architecture and returns it."""
+ # Retrieve a dictionary with raw information on the host's cpu
+ info = raw_info_dictionary()
+
+ # Get a list of possible candidates for this micro-architecture
+ candidates = compatible_microarchitectures(info)
+
+ # Reverse sort of the depth for the inheritance tree among only targets we
+ # can use. This gets the newest target we satisfy.
+ return sorted(
+ candidates, key=lambda t: (len(t.ancestors), len(t.features)), reverse=True
+ )[0]
+
+
+def compatibility_check(architecture_family):
+ """Decorator to register a function as a proper compatibility check.
+
+ A compatibility check function takes the raw info dictionary as a first
+ argument and an arbitrary target as the second argument. It returns True
+ if the target is compatible with the info dictionary, False otherwise.
+
+ Args:
+ architecture_family (str or tuple): architecture family for which
+ this test can be used, e.g. x86_64 or ppc64le etc.
+ """
+ # Turn the argument into something iterable
+ if isinstance(architecture_family, six.string_types):
+ architecture_family = (architecture_family,)
+
+ def decorator(func):
+ # pylint: disable=fixme
+ # TODO: on removal of Python 2.6 support this can be re-written as
+ # TODO: an update + a dict comprehension
+ for arch_family in architecture_family:
+ COMPATIBILITY_CHECKS[arch_family] = func
+
+ return func
+
+ return decorator
+
+
+@compatibility_check(architecture_family=("ppc64le", "ppc64"))
+def compatibility_check_for_power(info, target):
+ """Compatibility check for PPC64 and PPC64LE architectures."""
+ basename = platform.machine()
+ generation_match = re.search(r"POWER(\d+)", info.get("cpu", ""))
+ generation = int(generation_match.group(1))
+
+ # We can use a target if it descends from our machine type and our
+ # generation (9 for POWER9, etc) is at least its generation.
+ arch_root = TARGETS[basename]
+ return (
+ target == arch_root or arch_root in target.ancestors
+ ) and target.generation <= generation
+
+
+@compatibility_check(architecture_family="x86_64")
+def compatibility_check_for_x86_64(info, target):
+ """Compatibility check for x86_64 architectures."""
+ basename = "x86_64"
+ vendor = info.get("vendor_id", "generic")
+ features = set(info.get("flags", "").split())
+
+ # We can use a target if it descends from our machine type, is from our
+ # vendor, and we have all of its features
+ arch_root = TARGETS[basename]
+ return (
+ (target == arch_root or arch_root in target.ancestors)
+ and (target.vendor == vendor or target.vendor == "generic")
+ and target.features.issubset(features)
+ )
+
+
+@compatibility_check(architecture_family="aarch64")
+def compatibility_check_for_aarch64(info, target):
+ """Compatibility check for AARCH64 architectures."""
+ basename = "aarch64"
+ features = set(info.get("Features", "").split())
+ vendor = info.get("CPU implementer", "generic")
+
+ arch_root = TARGETS[basename]
+ return (
+ (target == arch_root or arch_root in target.ancestors)
+ and (target.vendor == vendor or target.vendor == "generic")
+ and target.features.issubset(features)
+ )