targets: Spack targets can now be fine-grained microarchitectures

Spack can now:

- label ppc64, ppc64le, x86_64, etc. builds with specific
  microarchitecture-specific names, like 'haswell', 'skylake' or
  'icelake'.

- detect the host architecture of a machine from /proc/cpuinfo or similar
  tools.

- Understand which microarchitectures are compatible with which (for
  binary reuse)

- Understand which compiler flags are needed (for GCC, so far) to build
  binaries for particular microarchitectures.

All of this is managed through a JSON file (microarchitectures.json) that
contains detailed auto-detection, compiler flag, and compatibility
information for specific microarchitecture targets.  The `llnl.util.cpu`
module implements a library that allows detection and comparison of
microarchitectures based on the data in this file.

The `target` part of Spack specs is now essentially a Microarchitecture
object, and Specs' targets can be compared for compatibility as well.
This allows us to label optimized binary packages at a granularity that
enables them to be reused on compatible machines.  Previously, we only
knew that a package was built for x86_64, NOT which x86_64 machines it
was usable on.

Currently this feature supports Intel, Power, and AMD chips. Support for
ARM is forthcoming.

Specifics:

- Add microarchitectures.json with descriptions of architectures

- Relaxed semantic of compiler's "target" attribute.  Before this change
  the semantic to check if a compiler could be viable for a given target
  was exact match. This made sense as the finest granularity of targets
  was architecture families.  As now we can target micro-architectures,
  this commit changes the semantic by interpreting as the architecture
  family what is stored in the compiler's "target" attribute. A compiler
  is then a viable choice if the target being concretized belongs to the
  same family. Similarly when a new compiler is detected the architecture
  family is stored in the "target" attribute.

- Make Spack's `cc` compiler wrapper inject target-specific flags on the
  command line

- Architecture concretization updated to use the same algorithm as
  compiler concretization

- Micro-architecture features, vendor, generation etc. are included in
  the package hash.  Generic architectures, such as x86_64 or ppc64, are
  still dumped using the name only.

- If the compiler for a target is not supported exit with an intelligible
  error message. If the compiler support is unknown don't try to use
  optimization flags.

- Support and define feature aliases (e.g., sse3 -> ssse3) in
  microarchitectures.json and on Microarchitecture objects. Feature
  aliases are defined in targets.json and map a name (the "alias") to a
  list of rules that must be met for the test to be successful. The rules
  that are available can be extended later using a decorator.

- Implement subset semantics for comparing microarchitectures (treat
  microarchitectures as a partial order, i.e. (a < b), (a == b) and (b <
  a) can all be false.

- Implement logic to automatically demote the default target if the
  compiler being used is too old to optimize for it. Updated docs to make
  this behavior explicit.  This avoids surprising the user if the default
  compiler is older than the host architecture.

This commit adds unit tests to verify the semantics of target ranges and
target lists in constraints. The implementation to allow target ranges
and lists is minimal and doesn't add any new type.  A more careful
refactor that takes into account the type system might be due later.

Co-authored-by: Gregory Becker <becker33.llnl.gov>
This commit is contained in:
Massimiliano Culpo 2019-06-19 15:47:07 +02:00 committed by Todd Gamblin
parent dfabf5d6b1
commit 3c4322bf1a
50 changed files with 3039 additions and 539 deletions

View file

@ -848,18 +848,111 @@ that executables will run without the need to set ``LD_LIBRARY_PATH``.
Architecture specifiers
^^^^^^^^^^^^^^^^^^^^^^^
The architecture can be specified by using the reserved
words ``target`` and/or ``os`` (``target=x86-64 os=debian7``). You can also
use the triplet form of platform, operating system and processor.
Each node in the dependency graph of a spec has an architecture attribute.
This attribute is a triplet of platform, operating system and processor.
You can specify the elements either separately, by using
the reserved keywords ``platform``, ``os`` and ``target``:
.. code-block:: console
$ spack install libelf platform=linux
$ spack install libelf os=ubuntu18.04
$ spack install libelf target=broadwell
or together by using the reserved keyword ``arch``:
.. code-block:: console
$ spack install libelf arch=cray-CNL10-haswell
Users on non-Cray systems won't have to worry about specifying the architecture.
Spack will autodetect what kind of operating system is on your machine as well
as the processor. For more information on how the architecture can be
used on Cray machines, see :ref:`cray-support`
Normally users don't have to bother specifying the architecture
if they are installing software for their current host as in that case the
values will be detected automatically.
.. admonition:: Cray machines
The situation is a little bit different for Cray machines and a detailed
explanation on how the architecture can be set on them can be found at :ref:`cray-support`
.. _support-for-microarchitectures:
"""""""""""""""""""""""""""""""""""""""
Support for specific microarchitectures
"""""""""""""""""""""""""""""""""""""""
Spack knows how to detect and optimize for many specific microarchitectures
(including recent Intel, AMD and IBM chips) and encodes this information in
the ``target`` portion of the architecture specification. A complete list of
the microarchitectures known to Spack can be obtained in the following way:
.. command-output:: spack arch --known-targets
When a spec is installed Spack matches the compiler being used with the
microarchitecture being targeted to inject appropriate optimization flags
at compile time. Giving a command such as the following:
.. code-block:: console
$ spack install zlib%gcc@9.0.1 target=icelake
will produce compilation lines similar to:
.. code-block:: console
$ /usr/bin/gcc-9 -march=icelake-client -mtune=icelake-client -c ztest10532.c
$ /usr/bin/gcc-9 -march=icelake-client -mtune=icelake-client -c -fPIC -O2 ztest10532.
...
where the flags ``-march=icelake-client -mtune=icelake-client`` are injected
by Spack based on the requested target and compiler.
If Spack knows that the requested compiler can't optimize for the current target
or can't build binaries for that target at all, it will exit with a meaningful error message:
.. code-block:: console
$ spack install zlib%gcc@5.5.0 target=icelake
==> Error: cannot produce optimized binary for micro-architecture "icelake" with gcc@5.5.0 [supported compiler versions are 8:]
When instead an old compiler is selected on a recent enough microarchitecture but there is
no explicit ``target`` specification, Spack will optimize for the best match it can find instead
of failing:
.. code-block:: console
$ spack arch
linux-ubuntu18.04-broadwell
$ spack spec zlib%gcc@4.8
Input spec
--------------------------------
zlib%gcc@4.8
Concretized
--------------------------------
zlib@1.2.11%gcc@4.8+optimize+pic+shared arch=linux-ubuntu18.04-haswell
$ spack spec zlib%gcc@9.0.1
Input spec
--------------------------------
zlib%gcc@9.0.1
Concretized
--------------------------------
zlib@1.2.11%gcc@9.0.1+optimize+pic+shared arch=linux-ubuntu18.04-broadwell
In the snippet above, for instance, the microarchitecture was demoted to ``haswell`` when
compiling with ``gcc@4.8`` since support to optimize for ``broadwell`` starts from ``gcc@4.9:``.
Finally if Spack has no information to match compiler and target, it will
proceed with the installation but avoid injecting any microarchitecture
specific flags.
.. warning::
Currently Spack doesn't print any warning to the user if it has no information
on which optimization flags should be used for a given compiler. This behavior
might change in the future.
.. _sec-virtual-dependencies:

View file

@ -3213,6 +3213,127 @@ the two functions is that ``satisfies()`` tests whether spec
constraints overlap at all, while ``in`` tests whether a spec or any
of its dependencies satisfy the provided spec.
^^^^^^^^^^^^^^^^^^^^^^^
Architecture specifiers
^^^^^^^^^^^^^^^^^^^^^^^
As mentioned in :ref:`support-for-microarchitectures` each node in a concretized spec
object has an architecture attribute which is a triplet of ``platform``, ``os`` and ``target``.
Each of these three items can be queried to take decisions when configuring, building or
installing a package.
""""""""""""""""""""""""""""""""""""""""""""""
Querying the platform and the operating system
""""""""""""""""""""""""""""""""""""""""""""""
Sometimes the actions to be taken to install a package might differ depending on the
platform we are installing for. If that is the case we can use conditionals:
.. code-block:: python
if spec.platform == 'darwin':
# Actions that are specific to Darwin
args.append('--darwin-specific-flag')
and branch based on the current spec platform. If we need to make a package directive
conditional on the platform we can instead employ the usual spec syntax and pass the
corresponding constraint to the appropriate argument of that directive:
.. code-block:: python
class Libnl(AutotoolsPackage):
conflicts('platform=darwin', msg='libnl requires FreeBSD or Linux')
Similar considerations are also valid for the ``os`` part of a spec's architecture.
For instance:
.. code-block:: python
class Glib(AutotoolsPackage)
patch('old-kernels.patch', when='os=centos6')
will apply the patch only when the operating system is Centos 6.
.. note::
Even though experienced Python programmers might recognize that there are other ways
to retrieve information on the platform:
.. code-block:: python
if sys.platform == 'darwin':
# Actions that are specific to Darwin
args.append('--darwin-specific-flag')
querying the spec architecture's platform should be considered the preferred. The key difference
is that a query on ``sys.platform``, or anything similar, is always bound to the host on which the
interpreter running Spack is located and as such it won't work correctly in environments where
cross-compilation is required.
"""""""""""""""""""""""""""""""""""""
Querying the target microarchitecture
"""""""""""""""""""""""""""""""""""""
The third item of the architecture tuple is the ``target`` which abstracts the information on the
CPU microarchitecture. A list of all the targets known to Spack can be obtained via the
command line:
.. command-output:: spack arch --known-targets
Within directives each of the names above can be used to match a particular target:
.. code-block:: python
class Julia(Package):
# This patch is only applied on icelake microarchitectures
patch("icelake.patch", when="target=icelake")
in a similar way to what we have seen before for ``platform`` and ``os``.
Where ``target`` objects really shine though is when they are used in methods
called at configure, build or install time. In that case we can test targets
for supported features, for instance:
.. code-block:: python
if 'avx512' in spec.target:
args.append('--with-avx512')
The snippet above will append the ``--with-avx512`` item to a list of arguments only if the corresponding
feature is supported by the current target. Sometimes we need to take different actions based
on the architecture family and not on the specific microarchitecture. In those cases
we can check the ``family`` attribute:
.. code-block:: python
if spec.target.family == 'ppc64le':
args.append('--enable-power')
Possible values for the ``family`` attribute are displayed by ``spack arch --known-targets``
under the "Generic architectures (families)" header.
Finally it's possible to perform actions based on whether the current microarchitecture
is compatible with a known one:
.. code-block:: python
if spec.target > 'haswell':
args.append('--needs-at-least-haswell')
The snippet above will add an item to a list of configure options only if the current
architecture is a superset of ``haswell`` or, said otherwise, only if the current
architecture is a later microarchitecture still compatible with ``haswell``.
.. admonition:: Using Spack on unknown microarchitectures
If Spack is used on an unknown microarchitecture it will try to perform a best match
of the features it detects and will select the closest microarchitecture it has
information for. In case nothing matches, it will create on the fly a new generic
architecture. This is done to allow users to still be able to use Spack
for their work. The software built won't be probably as optimized as it could but just
as you need a newer compiler to build for newer architectures, you may need newer
versions of Spack for new architectures to be correctly labeled.
^^^^^^^^^^^^^^^^^^^^^^
Accessing Dependencies
^^^^^^^^^^^^^^^^^^^^^^

2
lib/spack/env/cc vendored
View file

@ -374,7 +374,7 @@ case "$mode" in
CXX)
flags=("${flags[@]}" "${SPACK_CXXFLAGS[@]}") ;;
esac
args=(${SPACK_TARGET_ARGS[@]} "${args[@]}")
flags=(${SPACK_TARGET_ARGS[@]} "${flags[@]}")
;;
esac

View file

@ -0,0 +1,16 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from .microarchitecture import Microarchitecture, UnsupportedMicroarchitecture
from .microarchitecture import targets, generic_microarchitecture
from .detect import host
__all__ = [
'Microarchitecture',
'UnsupportedMicroarchitecture',
'targets',
'generic_microarchitecture',
'host'
]

View file

@ -0,0 +1,102 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
#: Known predicates that can be used to construct feature aliases
from .schema import targets_json, LazyDictionary, properties
_feature_alias_predicate = {}
class FeatureAliasTest(object):
"""A test that must be passed for a feature alias to succeed.
Args:
rules (dict): dictionary of rules to be met. Each key must be a
valid alias predicate
"""
def __init__(self, rules):
self.rules = rules
self.predicates = []
for name, args in rules.items():
self.predicates.append(_feature_alias_predicate[name](args))
def __call__(self, microarchitecture):
return all(
feature_test(microarchitecture) for feature_test in self.predicates
)
def _feature_aliases():
"""Returns the dictionary of all defined feature aliases."""
json_data = targets_json['feature_aliases']
aliases = {}
for alias, rules in json_data.items():
aliases[alias] = FeatureAliasTest(rules)
return aliases
feature_aliases = LazyDictionary(_feature_aliases)
def alias_predicate(predicate_schema):
"""Decorator to register a predicate that can be used to define
feature aliases.
Args:
predicate_schema (dict): schema to be enforced in
microarchitectures.json for the predicate
"""
def decorator(func):
name = func.__name__
# Check we didn't register anything else with the same name
if name in _feature_alias_predicate:
msg = 'the alias predicate "{0}" already exists'.format(name)
raise KeyError(msg)
# Update the overall schema
alias_schema = properties['feature_aliases']['patternProperties']
alias_schema[r'([\w]*)']['properties'].update(
{name: predicate_schema}
)
# Register the predicate
_feature_alias_predicate[name] = func
return func
return decorator
@alias_predicate(predicate_schema={'type': 'string'})
def reason(motivation_for_the_alias):
"""This predicate returns always True and it's there to allow writing
a documentation string in the JSON file to explain why an alias is needed.
"""
return lambda x: True
@alias_predicate(predicate_schema={
'type': 'array',
'items': {'type': 'string'}
})
def any_of(list_of_features):
"""Returns a predicate that is True if any of the feature in the
list is in the microarchitecture being tested, False otherwise.
"""
def _impl(microarchitecture):
return any(x in microarchitecture for x in list_of_features)
return _impl
@alias_predicate(predicate_schema={
'type': 'array',
'items': {'type': 'string'}
})
def families(list_of_families):
"""Returns a predicate that is True if the architecture family of
the microarchitecture being tested is in the list, False otherwise.
"""
def _impl(microarchitecture):
return str(microarchitecture.family) in list_of_families
return _impl

View file

@ -0,0 +1,216 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import collections
import functools
import platform
import re
import subprocess
import sys
import six
from .microarchitecture import generic_microarchitecture, targets
#: 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):
if sys.version_info[:2] == (2, 6):
return subprocess.run(
args, check=True, stdout=subprocess.PIPE).stdout # nopyqver
else:
return subprocess.check_output(args) # nopyqver
@info_dict(operating_system='Darwin')
def sysctl():
"""Returns a raw info dictionary parsing the output of sysctl."""
info = {}
info['vendor_id'] = check_output(
['sysctl', '-n', 'machdep.cpu.vendor']
).strip()
info['flags'] = check_output(
['sysctl', '-n', 'machdep.cpu.features']
).strip().lower()
info['flags'] += ' ' + check_output(
['sysctl', '-n', 'machdep.cpu.leaf7_features']
).strip().lower()
info['model'] = check_output(
['sysctl', '-n', 'machdep.cpu.model']
).strip()
info['model name'] = check_output(
['sysctl', '-n', 'machdep.cpu.brand_string']
).strip()
# Super hacky way to deal with slight representation differences
# Would be better to somehow consider these "identical"
if 'sse4.1' in info['flags']:
info['flags'] += ' sse4_1'
if 'sse4.2' in info['flags']:
info['flags'] += ' sse4_2'
if 'avx1.0' in info['flags']:
info['flags'] += ' avx'
return info
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.
"""
info = {}
for factory in info_factory[platform.system()]:
try:
info = factory()
except Exception:
pass
if 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), 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):
# 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):
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):
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)

View file

@ -0,0 +1,343 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import functools
import platform
import warnings
try:
from collections.abc import Sequence
except ImportError:
from collections import Sequence
import six
import llnl.util
import llnl.util.cpu.alias
import llnl.util.cpu.schema
from .schema import LazyDictionary
from .alias import feature_aliases
def coerce_target_names(func):
"""Decorator that automatically converts a known target name to a proper
Microarchitecture object.
"""
@functools.wraps(func)
def _impl(self, other):
if isinstance(other, six.string_types):
if other not in targets:
msg = '"{0}" is not a valid target name'
raise ValueError(msg.format(other))
other = targets[other]
return func(self, other)
return _impl
class Microarchitecture(object):
#: Aliases for micro-architecture's features
feature_aliases = feature_aliases
def __init__(
self, name, parents, vendor, features, compilers, generation=0
):
"""Represents a specific CPU micro-architecture.
Args:
name (str): name of the micro-architecture (e.g. skylake).
parents (list): list of parents micro-architectures, if any.
Parenthood is considered by cpu features and not
chronologically. As such each micro-architecture is
compatible with its ancestors. For example "skylake",
which has "broadwell" as a parent, supports running binaries
optimized for "broadwell".
vendor (str): vendor of the micro-architecture
features (list of str): supported CPU flags. Note that the semantic
of the flags in this field might vary among architectures, if
at all present. For instance x86_64 processors will list all
the flags supported by a given CPU while Arm processors will
list instead only the flags that have been added on top of the
base model for the current micro-architecture.
compilers (dict): compiler support to generate tuned code for this
micro-architecture. This dictionary has as keys names of
supported compilers, while values are list of dictionaries
with fields:
* name: name of the micro-architecture according to the
compiler. This is the name passed to the ``-march`` option
or similar. Not needed if the name is the same as that
passed in as argument above.
* versions: versions that support this micro-architecture.
generation (int): generation of the micro-architecture, if
relevant.
"""
self.name = name
self.parents = parents
self.vendor = vendor
self.features = features
self.compilers = compilers
self.generation = generation
@property
def ancestors(self):
value = self.parents[:]
for parent in self.parents:
value.extend(a for a in parent.ancestors if a not in value)
return value
def _to_set(self):
"""Returns a set of the nodes in this microarchitecture DAG."""
# This function is used to implement subset semantics with
# comparison operators
return set([str(self)] + [str(x) for x in self.ancestors])
@coerce_target_names
def __eq__(self, other):
if not isinstance(other, Microarchitecture):
return NotImplemented
return (self.name == other.name and
self.vendor == other.vendor and
self.features == other.features and
self.ancestors == other.ancestors and
self.compilers == other.compilers and
self.generation == other.generation)
@coerce_target_names
def __ne__(self, other):
return not self == other
@coerce_target_names
def __lt__(self, other):
if not isinstance(other, Microarchitecture):
return NotImplemented
return self._to_set() < other._to_set()
@coerce_target_names
def __le__(self, other):
return (self == other) or (self < other)
@coerce_target_names
def __gt__(self, other):
if not isinstance(other, Microarchitecture):
return NotImplemented
return self._to_set() > other._to_set()
@coerce_target_names
def __ge__(self, other):
return (self == other) or (self > other)
def __repr__(self):
cls_name = self.__class__.__name__
fmt = cls_name + '({0.name!r}, {0.parents!r}, {0.vendor!r}, ' \
'{0.features!r}, {0.compilers!r}, {0.generation!r})'
return fmt.format(self)
def __str__(self):
return self.name
def __contains__(self, feature):
# Feature must be of a string type, so be defensive about that
if not isinstance(feature, six.string_types):
msg = 'only objects of string types are accepted [got {0}]'
raise TypeError(msg.format(str(type(feature))))
# Here we look first in the raw features, and fall-back to
# feature aliases if not match was found
if feature in self.features:
return True
# Check if the alias is defined, if not it will return False
match_alias = Microarchitecture.feature_aliases.get(
feature, lambda x: False
)
return match_alias(self)
@property
def family(self):
"""Returns the architecture family a given target belongs to"""
roots = [x for x in [self] + self.ancestors if not x.ancestors]
msg = "a target is expected to belong to just one architecture family"
msg += "[found {0}]".format(', '.join(str(x) for x in roots))
assert len(roots) == 1, msg
return roots.pop()
def to_dict(self, return_list_of_items=False):
"""Returns a dictionary representation of this object.
Args:
return_list_of_items (bool): if True returns an ordered list of
items instead of the dictionary
"""
list_of_items = [
('name', str(self.name)),
('vendor', str(self.vendor)),
('features', sorted(
str(x) for x in self.features
)),
('generation', self.generation),
('parents', [str(x) for x in self.parents])
]
if return_list_of_items:
return list_of_items
return dict(list_of_items)
def optimization_flags(self, compiler, version):
"""Returns a string containing the optimization flags that needs
to be used to produce code optimized for this micro-architecture.
If there is no information on the compiler passed as argument the
function returns an empty string. If it is known that the compiler
version we want to use does not support this architecture the function
raises an exception.
Args:
compiler (str): name of the compiler to be used
version (str): version of the compiler to be used
"""
# If we don't have information on compiler return an empty string
if compiler not in self.compilers:
return ''
# If we have information on this compiler we need to check the
# version being used
compiler_info = self.compilers[compiler]
# Normalize the entries to have a uniform treatment in the code below
if not isinstance(compiler_info, Sequence):
compiler_info = [compiler_info]
def satisfies_constraint(entry, version):
min_version, max_version = entry['versions'].split(':')
# Check version suffixes
min_version, _, min_suffix = min_version.partition('-')
max_version, _, max_suffix = max_version.partition('-')
version, _, suffix = version.partition('-')
# If the suffixes are not all equal there's no match
if suffix != min_suffix or suffix != max_suffix:
return False
# Assume compiler versions fit into semver
tuplify = lambda x: tuple(int(y) for y in x.split('.'))
version = tuplify(version)
if min_version:
min_version = tuplify(min_version)
if min_version > version:
return False
if max_version:
max_version = tuplify(max_version)
if max_version < version:
return False
return True
for compiler_entry in compiler_info:
if satisfies_constraint(compiler_entry, version):
flags_fmt = compiler_entry['flags']
# If there's no field name, use the name of the
# micro-architecture
compiler_entry.setdefault('name', self.name)
# Check if we need to emit a warning
warning_message = compiler_entry.get('warnings', None)
if warning_message:
warnings.warn(warning_message)
flags = flags_fmt.format(**compiler_entry)
return flags
msg = ("cannot produce optimized binary for micro-architecture '{0}'"
" with {1}@{2} [supported compiler versions are {3}]")
msg = msg.format(self.name, compiler, version,
', '.join([x['versions'] for x in compiler_info]))
raise UnsupportedMicroarchitecture(msg)
def generic_microarchitecture(name):
"""Returns a generic micro-architecture with no vendor and no features.
Args:
name (str): name of the micro-architecture
"""
return Microarchitecture(
name, parents=[], vendor='generic', features=[], compilers={}
)
def _known_microarchitectures():
"""Returns a dictionary of the known micro-architectures. If the
current host platform is unknown adds it too as a generic target.
"""
# TODO: Simplify this logic using object_pairs_hook to OrderedDict
# TODO: when we stop supporting python2.6
def fill_target_from_dict(name, data, targets):
"""Recursively fills targets by adding the micro-architecture
passed as argument and all its ancestors.
Args:
name (str): micro-architecture to be added to targets.
data (dict): raw data loaded from JSON.
targets (dict): dictionary that maps micro-architecture names
to ``Microarchitecture`` objects
"""
values = data[name]
# Get direct parents of target
parent_names = values['from']
if isinstance(parent_names, six.string_types):
parent_names = [parent_names]
if parent_names is None:
parent_names = []
for p in parent_names:
# Recursively fill parents so they exist before we add them
if p in targets:
continue
fill_target_from_dict(p, data, targets)
parents = [targets.get(p) for p in parent_names]
vendor = values['vendor']
features = set(values['features'])
compilers = values.get('compilers', {})
generation = values.get('generation', 0)
targets[name] = Microarchitecture(
name, parents, vendor, features, compilers, generation
)
targets = {}
data = llnl.util.cpu.schema.targets_json['microarchitectures']
for name in data:
if name in targets:
# name was already brought in as ancestor to a target
continue
fill_target_from_dict(name, data, targets)
# Add the host platform if not present
host_platform = platform.machine()
targets.setdefault(host_platform, generic_microarchitecture(host_platform))
return targets
#: Dictionary of known micro-architectures
targets = LazyDictionary(_known_microarchitectures)
class UnsupportedMicroarchitecture(ValueError):
"""Raised if a compiler version does not support optimization for a given
micro-architecture.
"""

View file

@ -0,0 +1,832 @@
{
"microarchitectures": {
"x86": {
"from": null,
"vendor": "generic",
"features": []
},
"i686": {
"from": "x86",
"vendor": "GenuineIntel",
"features": []
},
"pentium2": {
"from": "i686",
"vendor": "GenuineIntel",
"features": [
"mmx"
]
},
"pentium3": {
"from": "pentium2",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse"
]
},
"pentium4": {
"from": "pentium3",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2"
]
},
"prescott": {
"from": "pentium4",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"sse3"
]
},
"x86_64": {
"from": null,
"vendor": "generic",
"features": [],
"compilers": {
"gcc": {
"versions": "4:",
"name": "x86-64",
"flags": "-march={name} -mtune={name}"
}
}
},
"nocona": {
"from": "x86_64",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"sse3"
],
"compilers": {
"gcc": {
"versions": "4:",
"flags": "-march={name} -mtune={name}"
}
}
},
"core2": {
"from": "nocona",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3"
],
"compilers": {
"gcc": {
"versions": "4:",
"flags": "-march={name} -mtune={name}"
}
}
},
"nehalem": {
"from": "core2",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt"
],
"compilers": {
"gcc": [
{
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
},
{
"versions": "4.6:4.8.5",
"name": "corei7",
"flags": "-march={name} -mtune={name}"
}
]
}
},
"westmere": {
"from": "nehalem",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq"
],
"compilers": {
"gcc": {
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
}
}
},
"sandybridge": {
"from": "westmere",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx"
],
"compilers": {
"gcc": [
{
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
},
{
"versions": "4.6:4.8.5",
"name": "corei7-avx",
"flags": "-march={name} -mtune={name}"
}
]
}
},
"ivybridge": {
"from": "sandybridge",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c"
],
"compilers": {
"gcc": [
{
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
},
{
"versions": ":4.8.5",
"name": "core-avx-i",
"flags": "-march={name} -mtune={name}"
}
]
}
},
"haswell": {
"from": "ivybridge",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2"
],
"compilers": {
"gcc": [
{
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
},
{
"versions": ":4.8.5",
"name": "core-avx2",
"flags": "-march={name} -mtune={name}"
}
]
}
},
"broadwell": {
"from": "haswell",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2",
"rdseed",
"adx"
],
"compilers": {
"gcc": {
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
}
}
},
"skylake": {
"from": "broadwell",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2",
"rdseed",
"adx",
"clflushopt",
"xsavec",
"xsaveopt"
],
"compilers": {
"gcc": {
"versions": "5.3:",
"flags": "-march={name} -mtune={name}"
}
}
},
"skylake_avx512": {
"from": "skylake",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2",
"rdseed",
"adx",
"clflushopt",
"xsavec",
"xsaveopt",
"avx512f",
"clwb",
"avx512vl",
"avx512bw",
"avx512dq",
"avx512cd"
],
"compilers": {
"gcc": {
"name": "skylake-avx512",
"versions": "5.3:",
"flags": "-march={name} -mtune={name}"
}
}
},
"cannonlake": {
"from": "skylake",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2",
"rdseed",
"adx",
"clflushopt",
"xsavec",
"xsaveopt",
"avx512f",
"avx512vl",
"avx512bw",
"avx512dq",
"avx512cd",
"avx512vbmi",
"avx512ifma",
"sha",
"umip"
],
"compilers": {
"gcc": {
"versions": "8:",
"flags": "-march={name} -mtune={name}"
}
}
},
"cascadelake": {
"from": "skylake_avx512",
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2",
"rdseed",
"adx",
"clflushopt",
"xsavec",
"xsaveopt",
"avx512f",
"clwb",
"avx512vl",
"avx512bw",
"avx512dq",
"avx512cd",
"avx512vnni"
],
"compilers": {
"gcc": {
"versions": "9:",
"flags": "-march={name} -mtune={name}"
}
}
},
"icelake": {
"from": [
"cascadelake",
"cannonlake"
],
"vendor": "GenuineIntel",
"features": [
"mmx",
"sse",
"sse2",
"ssse3",
"sse4_1",
"sse4_2",
"popcnt",
"aes",
"pclmulqdq",
"avx",
"rdrand",
"f16c",
"movbe",
"fma",
"avx2",
"bmi1",
"bmi2",
"rdseed",
"adx",
"clflushopt",
"xsavec",
"xsaveopt",
"avx512f",
"avx512vl",
"avx512bw",
"avx512dq",
"avx512cd",
"avx512vbmi",
"avx512ifma",
"sha",
"umip",
"clwb",
"rdpid",
"gfni",
"avx512vbmi2",
"avx512vpopcntdq",
"avx512bitalg",
"avx512vnni",
"vpclmulqdq",
"vaes"
],
"compilers": {
"gcc": {
"name": "icelake-client",
"versions": "8:",
"flags": "-march={name} -mtune={name}"
}
}
},
"barcelona": {
"from": "x86_64",
"vendor": "AuthenticAMD",
"features": [
"mmx",
"sse",
"sse2",
"sse4a",
"abm"
]
},
"bulldozer": {
"from": "barcelona",
"vendor": "AuthenticAMD",
"features": [
"mmx",
"sse",
"sse2",
"sse4a",
"abm",
"avx",
"xop",
"lwp",
"aes",
"pclmulqdq",
"cx16",
"ssse3",
"sse4_1",
"sse4_2"
],
"compilers": {
"gcc": {
"name": "bdver1",
"versions": "4.6:",
"flags": "-march={name} -mtune={name}"
}
}
},
"piledriver": {
"from": "bulldozer",
"vendor": "AuthenticAMD",
"features": [
"mmx",
"sse",
"sse2",
"sse4a",
"abm",
"avx",
"aes",
"pclmulqdq",
"cx16",
"ssse3",
"sse4_1",
"sse4_2",
"bmi1",
"f16c",
"fma"
],
"compilers": {
"gcc": {
"name": "bdver2",
"versions": "4.7:",
"flags": "-march={name} -mtune={name}"
}
}
},
"steamroller": {
"from": "piledriver",
"vendor": "AuthenticAMD",
"features": [
"mmx",
"sse",
"sse2",
"sse4a",
"abm",
"avx",
"aes",
"pclmulqdq",
"cx16",
"ssse3",
"sse4_1",
"sse4_2",
"bmi1",
"f16c",
"fma",
"fsgsbase"
],
"compilers": {
"gcc": {
"name": "bdver3",
"versions": "4.8:",
"flags": "-march={name} -mtune={name}"
}
}
},
"excavator": {
"from": "steamroller",
"vendor": "AuthenticAMD",
"features": [
"mmx",
"sse",
"sse2",
"sse4a",
"abm",
"avx",
"aes",
"pclmulqdq",
"cx16",
"ssse3",
"sse4_1",
"sse4_2",
"bmi1",
"f16c",
"fma",
"fsgsbase",
"bmi2",
"avx2",
"movbe"
],
"compilers": {
"gcc": {
"name": "bdver4",
"versions": "4.9:",
"flags": "-march={name} -mtune={name}"
}
}
},
"zen": {
"from": "excavator",
"vendor": "AuthenticAMD",
"features": [
"bmi1",
"bmi2",
"f16c",
"fma",
"fsgsbase",
"avx",
"avx2",
"rdseed",
"clzero",
"aes",
"pclmulqdq",
"cx16",
"movbe",
"mmx",
"sse",
"sse2",
"sse4a",
"ssse3",
"sse4_1",
"sse4_2",
"abm",
"xsavec",
"xsaveopt",
"clflushopt",
"popcnt"
],
"compilers": {
"gcc": {
"name": "znver1",
"versions": "6:",
"flags": "-march={name} -mtune={name}"
}
}
},
"zen2": {
"from": "zen",
"vendor": "AuthenticAMD",
"features": [
"bmi1",
"bmi2",
"f16c",
"fma",
"fsgsbase",
"avx",
"avx2",
"rdseed",
"clzero",
"aes",
"pclmulqdq",
"cx16",
"movbe",
"mmx",
"sse",
"sse2",
"sse4a",
"ssse3",
"sse4_1",
"sse4_2",
"abm",
"xsavec",
"xsaveopt",
"clflushopt",
"popcnt",
"clwb"
],
"compilers": {
"gcc": {
"name": "znver2",
"versions": "9:",
"flags": "-march={name} -mtune={name}"
}
}
},
"ppc64": {
"from": null,
"vendor": "generic",
"features": [],
"compilers": {
"gcc": {
"versions": "4:",
"flags": "-mcpu={name} -mtune={name}"
}
}
},
"power7": {
"from": "ppc64",
"vendor": "IBM",
"generation": 7,
"features": [],
"compilers": {
"gcc": {
"versions": "4.5:",
"flags": "-mcpu={name} -mtune={name}"
}
}
},
"power8": {
"from": "power7",
"vendor": "IBM",
"generation": 8,
"features": [],
"compilers": {
"gcc": [
{
"versions": "4.9:",
"flags": "-mcpu={name} -mtune={name}"
},
{
"versions": "4.8:4.8.5",
"warnings": "Using GCC 4.8 to optimize for Power 8 might not work if you are not on Red Hat Enterprise Linux 7, where a custom backport of the feature has been done. Upstream support from GCC starts in version 4.9",
"flags": "-mcpu={name} -mtune={name}"
}
]
}
},
"power9": {
"from": "power8",
"vendor": "IBM",
"generation": 9,
"features": [],
"compilers": {
"gcc": {
"versions": "6:",
"flags": "-mcpu={name} -mtune={name}"
}
}
},
"ppc64le": {
"from": null,
"vendor": "generic",
"features": [],
"compilers": {
"gcc": {
"versions": "4:",
"flags": "-mcpu={name} -mtune={name}"
}
}
},
"power8le": {
"from": "ppc64le",
"vendor": "IBM",
"generation": 8,
"features": [],
"compilers": {
"gcc": [
{
"versions": "4.9:",
"name": "power8",
"flags": "-mcpu={name} -mtune={name}"
},
{
"versions": "4.8:4.8.5",
"warnings": "Using GCC 4.8 to optimize for Power 8 might not work if you are not on Red Hat Enterprise Linux 7, where a custom backport of the feature has been done. Upstream support from GCC starts in version 4.9",
"name": "power8",
"flags": "-mcpu={name} -mtune={name}"
}
]
}
},
"power9le": {
"from": "power8le",
"vendor": "IBM",
"generation": 9,
"features": [],
"compilers": {
"gcc": {
"name": "power9",
"versions": "6:",
"flags": "-mcpu={name} -mtune={name}"
}
}
},
"aarch64": {
"from": null,
"vendor": "generic",
"features": [],
"compilers": {
"gcc": {
"versions": "4:",
"flags": "-march=armv8-a -mtune=generic"
}
}
}
},
"feature_aliases": {
"sse3": {
"reason": "ssse3 is a superset of sse3 and might be the only one listed",
"any_of": [
"ssse3"
]
},
"avx512": {
"reason": "avx512 indicates generic support for any of the avx512 instruction sets",
"any_of": [
"avx512f",
"avx512vl",
"avx512bw",
"avx512dq",
"avx512cd"
]
},
"altivec": {
"reason": "altivec is supported by Power PC architectures, but might not be listed in features",
"families": [
"ppc64le",
"ppc64"
]
},
"sse4.1": {
"reason": "permits to refer to sse4_1 also as sse4.1",
"any_of": [
"sse4_1"
]
},
"sse4.2": {
"reason": "permits to refer to sse4_2 also as sse4.2",
"any_of": [
"sse4_2"
]
}
}
}

View file

@ -0,0 +1,133 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import json
import os.path
try:
from collections.abc import MutableMapping
except ImportError:
from collections import MutableMapping
compilers_schema = {
'type': 'object',
'properties': {
'versions': {'type': 'string'},
'name': {'type': 'string'},
'flags': {'type': 'string'}
},
'required': ['versions', 'flags']
}
properties = {
'microarchitectures': {
'type': 'object',
'patternProperties': {
r'([\w]*)': {
'type': 'object',
'properties': {
'from': {
'anyOf': [
# More than one parent
{'type': 'array', 'items': {'type': 'string'}},
# Exactly one parent
{'type': 'string'},
# No parent
{'type': 'null'}
]
},
'vendor': {
'type': 'string'
},
'features': {
'type': 'array',
'items': {'type': 'string'}
},
'compilers': {
'type': 'object',
'patternProperties': {
r'([\w]*)': {
'anyOf': [
compilers_schema,
{
'type': 'array',
'items': compilers_schema
}
]
}
}
}
},
'required': ['from', 'vendor', 'features']
}
}
},
'feature_aliases': {
'type': 'object',
'patternProperties': {
r'([\w]*)': {
'type': 'object',
'properties': {},
'additionalProperties': False
}
},
}
}
schema = {
'$schema': 'http://json-schema.org/schema#',
'title': 'Schema for microarchitecture definitions and feature aliases',
'type': 'object',
'additionalProperties': False,
'properties': properties,
}
class LazyDictionary(MutableMapping):
"""Lazy dictionary that gets constructed on first access to any object key
Args:
factory (callable): factory function to construct the dictionary
"""
def __init__(self, factory, *args, **kwargs):
self.factory = factory
self.args = args
self.kwargs = kwargs
self._data = None
@property
def data(self):
if self._data is None:
self._data = self.factory(*self.args, **self.kwargs)
return self._data
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value
def __delitem__(self, key):
del self.data[key]
def __iter__(self):
return iter(self.data)
def __len__(self):
return len(self.data)
def _load_targets_json():
"""Loads ``microarchitectures.json`` in memory."""
directory_name = os.path.dirname(os.path.abspath(__file__))
filename = os.path.join(directory_name, 'microarchitectures.json')
with open(filename, 'r') as f:
return json.load(f)
#: In memory representation of the data in microarchitectures.json,
#: loaded on first access
targets_json = LazyDictionary(_load_targets_json)

View file

@ -1,226 +0,0 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import platform
import re
import subprocess
import sys
# Tuple of name, flags added, flags removed (default [])
_intel_32 = [
('i686', []),
('pentium2', ['mmx']),
('pentium3', ['sse']),
('pentium4', ['sse2']),
('prescott', ['sse3']),
]
_intel_64 = [ # commenting out the ones that aren't shown through sysctl
('nocona', ['mmx', 'sse', 'sse2', 'sse3']),#lm
('core2', ['ssse3'], ['sse3']),
('nehalem', ['sse4_1', 'sse4_2', 'popcnt']),
('westmere', ['aes', 'pclmulqdq']),
('sandybridge', ['avx']),
('ivybridge', ['rdrand', 'f16c']),#fsgsbase (is it RDWRFSGS on darwin?)
('haswell', ['movbe', 'fma', 'avx2', 'bmi1', 'bmi2']),
('broadwell', ['rdseed', 'adx']),
('skylake', ['xsavec', 'xsaves'])
]
# We will need to build on these and combine with names when intel releases
# further avx512 processors.
# _intel_avx12 = ['avx512f', 'avx512cd']
_amd_10_names = [
('barcelona', ['mmx', 'sse', 'sse2', 'sse3', 'sse4a', 'abm'])
]
_amd_14_names = [
('btver1', ['mmx', 'sse', 'sse2', 'sse3', 'ssse3', 'sse4a', 'cx16',
'abm']),#lm
]
_amd_15_names = [
('bdver1', ['avx', 'aes', 'pclmulqdq', 'cx16', 'sse', 'sse2', 'sse3',
'ssse3', 'sse4a', 'sse4_1', 'sse4_2', 'abm']),#xop, lwp
('bdver2', ['bmi1', 'f16c', 'fma',]),#tba?
('bdver3', ['fsgsbase']),
('bdver4', ['bmi2', 'movbe', 'avx2'])
]
_amd_16_names = [
('btver2', ['mmx', 'sse', 'sse2', 'sse3', 'ssse3', 'sse4a', 'cx16',
'abm', 'movbe', 'f16c', 'bmi1', 'avx', 'pclmulqdq',
'aes', 'sse4_1', 'sse4_2']),#lm
]
_amd_17_names = [
('znver1', ['bmi1', 'bmi2', 'f16c', 'fma', 'fsgsbase', 'avx', 'avx2',
'rdseed', 'mwaitx', 'clzero', 'aes', 'pclmulqdq', 'cx16',
'movbe', 'mmx', 'sse', 'sse2', 'sse3', 'ssse3', 'sse4a',
'sse4_1', 'sse4_2', 'abm', 'xsavec', 'xsaves',
'clflushopt', 'popcnt', 'adcx'])
]
_amd_numbers = {
0x10: _amd_10_names,
0x14: _amd_14_names,
0x15: _amd_15_names,
0x16: _amd_16_names,
0x17: _amd_17_names
}
def supported_target_names():
intel_names = set(t[0] for t in _intel_64)
intel_names |= set(t[0] for t in _intel_32)
amd_names = set()
for family in _amd_numbers:
amd_names |= set(t[0] for t in _amd_numbers[family])
power_names = set('power' + str(d) for d in range(7, 10))
return intel_names | amd_names | power_names
def create_dict_from_cpuinfo():
# Initialize cpuinfo from file
cpuinfo = {}
try:
with open('/proc/cpuinfo') as file:
text = file.readlines()
for line in text:
if line.strip():
key, _, value = line.partition(':')
cpuinfo[key.strip()] = value.strip()
except IOError:
return None
return cpuinfo
def check_output(args):
if sys.version_info >= (3, 0):
return subprocess.run(args, check=True, stdout=PIPE).stdout # nopyqver
else:
return subprocess.check_output(args) # nopyqver
def create_dict_from_sysctl():
cpuinfo = {}
try:
cpuinfo['vendor_id'] = check_output(['sysctl', '-n',
'machdep.cpu.vendor']).strip()
cpuinfo['flags'] = check_output(['sysctl', '-n',
'machdep.cpu.features']).strip().lower()
cpuinfo['flags'] += ' ' + check_output(['sysctl', '-n',
'machdep.cpu.leaf7_features']).strip().lower()
cpuinfo['model'] = check_output(['sysctl', '-n',
'machdep.cpu.model']).strip()
cpuinfo['model name'] = check_output(['sysctl', '-n',
'machdep.cpu.brand_string']).strip()
# Super hacky way to deal with slight representation differences
# Would be better to somehow consider these "identical"
if 'sse4.1' in cpuinfo['flags']:
cpuinfo['flags'] += ' sse4_1'
if 'sse4.2' in cpuinfo['flags']:
cpuinfo['flags'] += ' sse4_2'
if 'avx1.0' in cpuinfo['flags']:
cpuinfo['flags'] += ' avx'
except:
pass
return cpuinfo
def get_cpu_name():
name = get_cpu_name_helper(platform.system())
return name if name else platform.machine()
def get_cpu_name_helper(system):
# TODO: Elsewhere create dict of codenames (targets) and flag sets.
# Return cpu name or an empty string if one cannot be determined.
cpuinfo = {}
if system == 'Linux':
cpuinfo = create_dict_from_cpuinfo()
elif system == 'Darwin':
cpuinfo = create_dict_from_sysctl()
if not cpuinfo:
return ''
if 'vendor_id' in cpuinfo and cpuinfo['vendor_id'] == 'GenuineIntel':
if 'model name' not in cpuinfo or 'flags' not in cpuinfo:
# We don't have the information we need to determine the
# microarchitecture name
return ''
return get_intel_cpu_name(cpuinfo)
elif 'vendor_id' in cpuinfo and cpuinfo['vendor_id'] == 'AuthenticAMD':
if 'cpu family' not in cpuinfo or 'flags' not in cpuinfo:
# We don't have the information we need to determine the
# microarchitecture name
return ''
return get_amd_cpu_name(cpuinfo)
elif 'cpu' in cpuinfo and 'POWER' in cpuinfo['cpu']:
return get_ibm_cpu_name(cpuinfo['cpu'])
else:
return ''
def get_ibm_cpu_name(cpu):
power_pattern = re.compile('POWER(\d+)')
power_match = power_pattern.search(cpu)
if power_match:
if 'le' in platform.machine():
return 'power' + power_match.group(1) + 'le'
return 'power' + power_match.group(1)
else:
return ''
def get_intel_cpu_name(cpuinfo):
model_name = cpuinfo['model name']
if 'Atom' in model_name:
return 'atom'
elif 'Quark' in model_name:
return 'quark'
elif 'Xeon' in model_name and 'Phi' in model_name:
# This is hacky and needs to be extended for newer avx512 chips
return 'knl'
else:
ret = ''
flag_list = cpuinfo['flags'].split()
proc_flags = []
for _intel_processors in [_intel_32, _intel_64]:
for entry in _intel_processors:
try:
proc, flags_added, flags_removed = entry
except ValueError:
proc, flags_added = entry
flags_removed = []
proc_flags = list(filter(lambda x: x not in flags_removed, proc_flags))
proc_flags.extend(flags_added)
if all(f in flag_list for f in proc_flags):
ret = proc
return ret
def get_amd_cpu_name(cpuinfo):
#TODO: Learn what the "canonical" granularity of naming
# is for AMD processors, implement dict as for intel.
ret = ''
flag_list = cpuinfo['flags'].split()
model_number = int(cpuinfo['cpu family'])
flags_dict = _amd_numbers[model_number]
proc_flags = []
for proc, proc_flags_added in flags_dict:
proc_flags.extend(proc_flags_added)
if all(f in flag_list for f in proc_flags):
ret = proc
else:
break
return ret
"""IDEA: In build_environment.setup_compiler_environment, include a
call to compiler.tuning_flags(spec.architecture.target). For gcc this
would return "-march=%s" % str(spec.architecture.target). We only call
this if the target is a valid tuning target (I.e. not
platform.machine(), but a more specific target we successfully
discovered.
Then set
SPACK_TUNING_FLAGS=compiler.tuning_flags(spec.architecture.target)
This way the compiler wrapper can just add $SPACK_TUNING_FLAGS to the
eventual command."""

View file

@ -56,50 +56,137 @@
attributes front_os and back_os. The operating system as described earlier,
will be responsible for compiler detection.
"""
import functools
import inspect
import six
import llnl.util.cpu as cpu
import llnl.util.tty as tty
from llnl.util.lang import memoized, list_modules, key_ordering
from llnl.util.cpu_name import get_cpu_name
import spack.compiler
import spack.paths
import spack.error as serr
import spack.version
from spack.util.naming import mod_to_class
from spack.util.spack_yaml import syaml_dict
class NoPlatformError(serr.SpackError):
def __init__(self):
super(NoPlatformError, self).__init__(
"Could not determine a platform for this machine.")
@key_ordering
class Target(object):
""" Target is the processor of the host machine.
The host machine may have different front-end and back-end targets,
especially if it is a Cray machine. The target will have a name and
also the module_name (e.g craype-compiler). Targets will also
recognize which platform they came from using the set_platform method.
Targets will have compiler finding strategies
def _ensure_other_is_target(method):
"""Decorator to be used in dunder methods taking a single argument to
ensure that the argument is an instance of ``Target`` too.
"""
@functools.wraps(method)
def _impl(self, other):
if isinstance(other, six.string_types):
other = Target(other)
if not isinstance(other, Target):
return NotImplemented
return method(self, other)
return _impl
class Target(object):
def __init__(self, name, module_name=None):
self.name = name # case of cray "ivybridge" but if it's x86_64
self.module_name = module_name # craype-ivybridge
"""Target models microarchitectures and their compatibility.
# Sets only the platform name to avoid recursiveness
Args:
name (str or Microarchitecture):micro-architecture of the
target
module_name (str): optional module name to get access to the
current target. This is typically used on machines
like Cray (e.g. craype-compiler)
"""
if not isinstance(name, cpu.Microarchitecture):
name = cpu.targets.get(
name, cpu.generic_microarchitecture(name)
)
self.microarchitecture = name
self.module_name = module_name
def _cmp_key(self):
return (self.name, self.module_name)
@property
def name(self):
return self.microarchitecture.name
@_ensure_other_is_target
def __eq__(self, other):
return self.microarchitecture == other.microarchitecture and \
self.module_name == other.module_name
def __ne__(self, other):
# This method is necessary as long as we support Python 2. In Python 3
# __ne__ defaults to the implementation below
return not self == other
@_ensure_other_is_target
def __lt__(self, other):
# TODO: In the future it would be convenient to say
# TODO: `spec.architecture.target < other.architecture.target`
# TODO: and change the semantic of the comparison operators
# This is needed to sort deterministically specs in a list.
# It doesn't implement a total ordering semantic.
return self.microarchitecture.name < other.microarchitecture.name
def __hash__(self):
return hash((self.name, self.module_name))
@staticmethod
def from_dict_or_value(dict_or_value):
# A string here represents a generic target (like x86_64 or ppc64) or
# a custom micro-architecture
if isinstance(dict_or_value, six.string_types):
return Target(dict_or_value)
# TODO: From a dict we actually retrieve much more information than
# TODO: just the name. We can use that information to reconstruct an
# TODO: "old" micro-architecture or check the current definition.
target_info = dict_or_value
return Target(target_info['name'])
def to_dict_or_value(self):
"""Returns a dict or a value representing the current target.
String values are used to keep backward compatibility with generic
targets, like e.g. x86_64 or ppc64. More specific micro-architectures
will return a dictionary which contains information on the name,
features, vendor, generation and parents of the current target.
"""
# Generic targets represent either an architecture
# family (like x86_64) or a custom micro-architecture
if self.microarchitecture.vendor == 'generic':
return str(self)
return syaml_dict(
self.microarchitecture.to_dict(return_list_of_items=True)
)
def __repr__(self):
return self.__str__()
cls_name = self.__class__.__name__
fmt = cls_name + '({0}, {1})'
return fmt.format(repr(self.microarchitecture),
repr(self.module_name))
def __str__(self):
return self.name
return str(self.microarchitecture)
def __contains__(self, cpu_flag):
return cpu_flag in self.microarchitecture
def optimization_flags(self, compiler):
return self.microarchitecture.optimization_flags(
compiler.name, str(compiler.version)
)
@key_ordering
@ -142,6 +229,8 @@ def target(self, name):
front-end, and back-end. This can be overwritten
by a subclass for which we want to provide further aliasing options.
"""
# TODO: Check if we can avoid using strings here
name = str(name)
if name == 'default_target':
name = self.default
elif name == 'frontend' or name == 'fe':
@ -296,7 +385,7 @@ def _cmp_key(self):
else:
os = self.os
if isinstance(self.target, Target):
target = self.target.name
target = self.target.microarchitecture
else:
target = self.target
return (platform, os, target)
@ -306,7 +395,7 @@ def to_dict(self):
d = syaml_dict([
('platform', str_or_none(self.platform)),
('platform_os', str_or_none(self.os)),
('target', str_or_none(self.target))])
('target', self.target.to_dict_or_value())])
return syaml_dict([('arch', d)])
@staticmethod
@ -336,9 +425,9 @@ def verify_platform(platform_name):
def arch_for_spec(arch_spec):
"""Transforms the given architecture spec into an architecture objct."""
"""Transforms the given architecture spec into an architecture object."""
arch_spec = spack.spec.ArchSpec(arch_spec)
assert(arch_spec.concrete)
assert arch_spec.concrete
arch_plat = get_platform(arch_spec.platform)
if not (arch_plat.operating_system(arch_spec.os) and

View file

@ -200,23 +200,9 @@ def set_compiler_environment_variables(pkg, env):
env.set('SPACK_F77_RPATH_ARG', compiler.f77_rpath_arg)
env.set('SPACK_FC_RPATH_ARG', compiler.fc_rpath_arg)
# Set the tuning parameters that the compiler will add
isa_target = compiler.isa_name_for_target(spec.architecture.target)
if spec.variants['tuning'].value == 'generic':
tuning_target = 'generic'
else:
tuning_target = compiler.tuning_name_for_target(
spec.architecture.target
)
if compiler.isa_flag and isa_target:
isa_arg = '{0}={1}'.format(compiler.isa_flag, isa_target)
else:
isa_arg = ''
if compiler.tuning_flag and tuning_target:
tuning_arg = '{0}={1}'.format(compiler.tuning_flag, tuning_target)
else:
tuning_arg = ''
env.set('SPACK_TARGET_ARGS', '{0} {1}'.format(isa_arg, tuning_arg))
# Set the target parameters that the compiler will add
isa_arg = spec.architecture.target.optimization_flags(compiler)
env.set('SPACK_TARGET_ARGS', isa_arg)
# Trap spack-tracked compiler flags as appropriate.
# env_flags are easy to accidentally override.

View file

@ -10,6 +10,8 @@
import sys
import argparse
import six
import llnl.util.tty as tty
from llnl.util.lang import attr_setdefault, index_by
from llnl.util.tty.colify import colify
@ -132,7 +134,7 @@ def parse_specs(args, **kwargs):
tests = kwargs.get('tests', False)
try:
sargs = args if isinstance(args, basestring) else ' '.join(args)
sargs = args if isinstance(args, six.string_types) else ' '.join(args)
specs = spack.spec.parse(sargs)
for spec in specs:
if concretize:

View file

@ -5,6 +5,11 @@
from __future__ import print_function
import collections
import llnl.util.cpu
import llnl.util.tty.colify as colify
import llnl.util.tty.color as color
import spack.architecture as architecture
description = "print architecture information about this machine"
@ -13,6 +18,10 @@
def setup_parser(subparser):
subparser.add_argument(
'--known-targets', action='store_true',
help='show a list of all known targets and exit'
)
parts = subparser.add_mutually_exclusive_group()
parts2 = subparser.add_mutually_exclusive_group()
parts.add_argument(
@ -32,7 +41,41 @@ def setup_parser(subparser):
help='print backend')
def display_targets(targets):
"""Prints a human readable list of the targets passed as argument."""
by_vendor = collections.defaultdict(list)
for _, target in targets.items():
by_vendor[target.vendor].append(target)
def display_target_group(header, target_group):
print(header)
colify.colify(target_group, indent=4)
print('')
generic_architectures = by_vendor.pop('generic', None)
if generic_architectures:
header = color.colorize(r'@*B{Generic architectures (families)}')
group = sorted(generic_architectures, key=lambda x: str(x))
display_target_group(header, group)
for vendor, vendor_targets in by_vendor.items():
by_family = collections.defaultdict(list)
for t in vendor_targets:
by_family[str(t.family)].append(t)
for family, group in by_family.items():
vendor = color.colorize(r'@*B{' + vendor + r'}')
family = color.colorize(r'@*B{' + family + r'}')
header = ' - '.join([vendor, family])
group = sorted(group, key=lambda x: len(x.ancestors))
display_target_group(header, group)
def arch(parser, args):
if args.known_targets:
display_targets(llnl.util.cpu.targets)
return
if args.frontend:
arch = architecture.Arch(architecture.platform(),
'frontend', 'frontend')

View file

@ -83,7 +83,7 @@ def compiler_find(args):
compilers = [c for c in spack.compilers.find_compilers(paths)]
new_compilers = []
for c in compilers:
arch_spec = ArchSpec(None, c.operating_system, c.target)
arch_spec = ArchSpec((None, c.operating_system, c.target))
same_specs = spack.compilers.compilers_for_spec(
c.spec, arch_spec, init_config=False)

View file

@ -153,7 +153,7 @@ def mirror_create(args):
"""Create a directory to be used as a spack mirror, and fill it with
package archives."""
# try to parse specs from the command line first.
with spack.concretize.concretizer.disable_compiler_existence_check():
with spack.concretize.disable_compiler_existence_check():
specs = spack.cmd.parse_specs(args.specs, concretize=True)
# If there is a file, parse each line as a spec and add it to the list.

View file

@ -222,14 +222,6 @@ def f77_rpath_arg(self):
def fc_rpath_arg(self):
return '-Wl,-rpath,'
@property
def isa_flag(self):
return '-march'
@property
def tuning_flag(self):
return '-mtune'
# Cray PrgEnv name that can be used to load this compiler
PrgEnv = None
# Name of module used to switch versions of this compiler
@ -428,12 +420,6 @@ def f77_version(cls, f77):
def fc_version(cls, fc):
return cls.default_version(fc)
def isa_name_for_target(self, target):
return str(target)
def tuning_name_for_target(self, target):
return str(target)
@classmethod
def search_regexps(cls, language):
# Compile all the regular expressions used for files beforehand.

View file

@ -10,13 +10,12 @@
import itertools
import multiprocessing.pool
import os
import platform as py_platform
import six
import llnl.util.lang
import llnl.util.filesystem as fs
import llnl.util.tty as tty
from llnl.util.cpu_name import get_cpu_name
import llnl.util.cpu as cpu
import spack.paths
import spack.error
@ -402,8 +401,18 @@ def get_compilers(config, cspec=None, arch_spec=None):
# any given arch spec. If the compiler has no assigned
# target this is an old compiler config file, skip this logic.
target = items.get('target', None)
if arch_spec and target and (target != arch_spec.target and
target != 'any'):
try:
current_target = llnl.util.cpu.targets[str(arch_spec.target)]
family = str(current_target.family)
except KeyError:
# TODO: Check if this exception handling makes sense, or if we
# TODO: need to change / refactor tests
family = arch_spec.target
except AttributeError:
assert arch_spec is None
if arch_spec and target and (target != family and target != 'any'):
continue
compilers.append(_compiler_from_config_entry(items))
@ -646,8 +655,9 @@ def _default(cmp_id, paths):
compiler_cls = spack.compilers.class_for_compiler_name(compiler_name)
spec = spack.spec.CompilerSpec(compiler_cls.name, version)
paths = [paths.get(l, None) for l in ('cc', 'cxx', 'f77', 'fc')]
target = cpu.host()
compiler = compiler_cls(
spec, operating_system, get_cpu_name(), paths
spec, operating_system, str(target.family), paths
)
return [compiler]

View file

@ -16,6 +16,7 @@
"""
from __future__ import print_function
import platform
import os.path
import tempfile
import llnl.util.filesystem as fs
@ -24,9 +25,9 @@
from itertools import chain
from functools_backport import reverse_order
from contextlib import contextmanager
from six import iteritems
import llnl.util.lang
import llnl.util.cpu as cpu
import spack.repo
import spack.abi
@ -40,10 +41,6 @@
from spack.package_prefs import PackagePrefs, spec_externals, is_spec_buildable
#: Concretizer singleton
concretizer = llnl.util.lang.Singleton(lambda: Concretizer())
#: impements rudimentary logic for ABI compatibility
_abi = llnl.util.lang.Singleton(lambda: spack.abi.ABI())
@ -52,25 +49,17 @@ class Concretizer(object):
"""You can subclass this class to override some of the default
concretization strategies, or you can override all of them.
"""
def __init__(self):
# controls whether we check that compiler versions actually exist
# during concretization. Used for testing and for mirror creation
self.check_for_compiler_existence = not config.get(
'config:install_missing_compilers', False)
#: Controls whether we check that compiler versions actually exist
#: during concretization. Used for testing and for mirror creation
check_for_compiler_existence = None
@contextmanager
def disable_compiler_existence_check(self):
saved = self.check_for_compiler_existence
self.check_for_compiler_existence = False
yield
self.check_for_compiler_existence = saved
@contextmanager
def enable_compiler_existence_check(self):
saved = self.check_for_compiler_existence
self.check_for_compiler_existence = True
yield
self.check_for_compiler_existence = saved
def __init__(self, abstract_spec=None):
if Concretizer.check_for_compiler_existence is None:
Concretizer.check_for_compiler_existence = not config.get(
'config:install_missing_compilers', False
)
self.abstract_spec = abstract_spec
self._adjust_target_answer_generator = None
def _valid_virtuals_and_externals(self, spec):
"""Returns a list of candidate virtual dep providers and external
@ -234,30 +223,91 @@ def concretize_architecture(self, spec):
DAG has an architecture, then use the root otherwise use the defaults
on the platform.
"""
try:
# Get the nearest architecture with any fields set
nearest = next(p for p in spec.traverse(direction='parents')
if (p.architecture and p is not spec))
nearest_arch = nearest.architecture
except StopIteration:
# Default to the system architecture if nothing set
nearest_arch = spack.spec.ArchSpec(spack.architecture.sys_type())
spec_changed = False
# ensure type safety for the architecture
if spec.architecture is None:
spec.architecture = spack.spec.ArchSpec()
spec_changed = True
# replace each of the fields (platform, os, target) separately
nearest_dict = nearest_arch.to_cmp_dict()
replacement_fields = [k for k, v in iteritems(nearest_dict)
if v and not getattr(spec.architecture, k)]
for field in replacement_fields:
setattr(spec.architecture, field, getattr(nearest_arch, field))
spec_changed = True
if spec.architecture.platform and \
(spec.architecture.os and spec.architecture.target):
return False
# Get platform of nearest spec with a platform, including spec
# If spec has a platform, easy
if spec.architecture.platform:
new_plat = spack.architecture.get_platform(
spec.architecture.platform)
else:
# Else if anyone else has a platform, take the closest one
# Search up, then down, along build/link deps first
# Then any nearest. Algorithm from compilerspec search
platform_spec = find_spec(
spec, lambda x: x.architecture and x.architecture.platform
)
if platform_spec:
new_plat = spack.architecture.get_platform(
platform_spec.architecture.platform)
else:
# If no platform anywhere in this spec, grab the default
new_plat = spack.architecture.platform()
# Get nearest spec with relevant platform and an os
# Generally, same algorithm as finding platform, except we only
# consider specs that have a platform
if spec.architecture.os:
new_os = spec.architecture.os
else:
new_os_spec = find_spec(
spec, lambda x: (x.architecture and
x.architecture.platform == str(new_plat) and
x.architecture.os)
)
if new_os_spec:
new_os = new_os_spec.architecture.os
else:
new_os = new_plat.operating_system('default_os')
# Get the nearest spec with relevant platform and a target
# Generally, same algorithm as finding os
if spec.architecture.target:
new_target = spec.architecture.target
else:
new_target_spec = find_spec(
spec, lambda x: (x.architecture and
x.architecture.platform == str(new_plat) and
x.architecture.target)
)
if new_target_spec:
new_target = new_target_spec.architecture.target
else:
# To get default platform, consider package prefs
if PackagePrefs.has_preferred_targets(spec.name):
target_prefs = PackagePrefs(spec.name, 'target')
target_specs = [spack.spec.Spec('target=%s' % tname)
for tname in cpu.targets]
def tspec_filter(s):
# Filter target specs by whether the architecture
# family is the current machine type. This ensures
# we only consider x86_64 targets when on an
# x86_64 machine, etc. This may need to change to
# enable setting cross compiling as a default
target = cpu.targets[s.architecture.target]
arch_family_name = target.family.name
return arch_family_name == platform.machine()
# Sort filtered targets by package prefs
target_specs = list(filter(tspec_filter, target_specs))
target_specs.sort(key=target_prefs)
new_target = target_specs[0].architecture.target
else:
new_target = new_plat.target('default_target')
# Construct new architecture, compute whether spec changed
arch_spec = (str(new_plat), str(new_os), str(new_target))
new_arch = spack.spec.ArchSpec(arch_spec)
spec_changed = new_arch != spec.architecture
spec.architecture = new_arch
return spec_changed
def concretize_variants(self, spec):
@ -316,7 +366,7 @@ def _proper_compiler_style(cspec, aspec):
other_spec = spec if spec.compiler else find_spec(
spec, lambda x: x.compiler, spec.root)
other_compiler = other_spec.compiler
assert(other_spec)
assert other_spec
# Check if the compiler is already fully specified
if other_compiler and other_compiler.concrete:
@ -368,7 +418,7 @@ def _proper_compiler_style(cspec, aspec):
_compiler_concretization_failure(
other_compiler, spec.architecture)
assert(spec.compiler.concrete)
assert spec.compiler.concrete
return True # things changed.
def concretize_compiler_flags(self, spec):
@ -429,6 +479,94 @@ def concretize_compiler_flags(self, spec):
return ret
def adjust_target(self, spec):
"""Adjusts the target microarchitecture if the compiler is too old
to support the default one.
Args:
spec: spec to be concretized
Returns:
True if spec was modified, False otherwise
"""
# To minimize the impact on performance this function will attempt
# to adjust the target only at the very first call. It will just
# return False on subsequent calls. The way this is achieved is by
# initializing a generator and making this function return the next
# answer.
def _make_only_one_call(spec):
yield self._adjust_target(spec)
while True:
yield False
if self._adjust_target_answer_generator is None:
self._adjust_target_answer_generator = _make_only_one_call(spec)
return next(self._adjust_target_answer_generator)
def _adjust_target(self, spec):
"""Assumes that the architecture and the compiler have been
set already and checks if the current target microarchitecture
is the default and can be optimized by the compiler.
If not, downgrades the microarchitecture until a suitable one
is found. If none can be found raise an error.
Args:
spec: spec to be concretized
Returns:
True if any modification happened, False otherwise
"""
import llnl.util.cpu
# Try to adjust the target only if it is the default
# target for this platform
current_target = spec.architecture.target
current_platform = spack.architecture.get_platform(
spec.architecture.platform
)
if current_target != current_platform.target('default_target') or \
(self.abstract_spec.architecture is not None and
self.abstract_spec.architecture.target is not None):
return False
try:
current_target.optimization_flags(spec.compiler)
except llnl.util.cpu.UnsupportedMicroarchitecture:
microarchitecture = current_target.microarchitecture
for ancestor in microarchitecture.ancestors:
candidate = None
try:
candidate = spack.architecture.Target(ancestor)
candidate.optimization_flags(spec.compiler)
except llnl.util.cpu.UnsupportedMicroarchitecture:
continue
if candidate is not None:
spec.architecture.target = candidate
return True
else:
raise
return False
@contextmanager
def disable_compiler_existence_check():
saved = Concretizer.check_for_compiler_existence
Concretizer.check_for_compiler_existence = False
yield
Concretizer.check_for_compiler_existence = saved
@contextmanager
def enable_compiler_existence_check():
saved = Concretizer.check_for_compiler_existence
Concretizer.check_for_compiler_existence = True
yield
Concretizer.check_for_compiler_existence = saved
def find_spec(spec, condition, default=None):
"""Searches the dag from spec in an intelligent order and looks

View file

@ -17,6 +17,7 @@
import pstats
import argparse
import traceback
import warnings
from six import StringIO
import llnl.util.tty as tty
@ -389,8 +390,16 @@ def make_argument_parser(**kwargs):
return parser
def send_warning_to_tty(message, *args):
"""Redirects messages to tty.warn."""
tty.warn(message)
def setup_main_options(args):
"""Configure spack globals based on the basic options."""
# Assign a custom function to show warnings
warnings.showwarning = send_warning_to_tty
# Set up environment based on args.
tty.set_verbose(args.verbose)
tty.set_debug(args.debug)

View file

@ -61,7 +61,6 @@
from spack.util.package_hash import package_hash
from spack.version import Version
from spack.package_prefs import get_package_dir_permissions, get_package_group
from spack.directives import variant
"""Allowed URL schemes for spack packages."""
_ALLOWED_URL_SCHEMES = ["http", "https", "ftp", "file", "git"]
@ -508,10 +507,6 @@ class PackageBase(with_metaclass(PackageMeta, PackageViewMixin, object)):
metadata_attrs = ['homepage', 'url', 'list_url', 'extendable', 'parallel',
'make_jobs']
# Add the universal variant "tuning" with values generic | specific
variant('tuning', values=('generic', 'specific'), default='generic',
description='Set compiler tuning generic or to target')
def __init__(self, spec):
# this determines how the package should be built.
self.spec = spec

View file

@ -137,7 +137,10 @@ def order_for_package(cls, pkgname, component, vpkg=None, all=True):
order = order.get(vpkg)
if order:
return [str(s).strip() for s in order]
ret = [str(s).strip() for s in order]
if component == 'target':
ret = ['target=%s' % tname for tname in ret]
return ret
return []
@ -168,6 +171,11 @@ def has_preferred_providers(cls, pkgname, vpkg):
"""Whether specific package has a preferred vpkg providers."""
return bool(cls.order_for_package(pkgname, 'providers', vpkg, False))
@classmethod
def has_preferred_targets(cls, pkg_name):
"""Whether specific package has a preferred vpkg providers."""
return bool(cls.order_for_package(pkg_name, 'target'))
@classmethod
def preferred_variants(cls, pkg_name):
"""Return a VariantMap of preferred variants/values for a spec."""

View file

@ -4,7 +4,7 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import platform
from llnl.util.cpu_name import get_cpu_name
import llnl.util.cpu as cpu
from spack.architecture import Platform, Target
from spack.operating_systems.mac_os import MacOs
@ -15,33 +15,13 @@ class Darwin(Platform):
def __init__(self):
super(Darwin, self).__init__('darwin')
# TODO: These are probably overkill
# Add Intel architectures
self.add_target('haswell', Target('haswell'))
self.add_target('broadwell', Target('broadwell'))
self.add_target('ivybridge', Target('ivybridge'))
self.add_target('sandybridge', Target('sandybridge'))
self.add_target('core2', Target('core2'))
for name in cpu.targets:
self.add_target(name, Target(name))
# Add "basic" architectures
self.add_target('x86_64', Target('x86_64'))
self.add_target('ppc64le', Target('ppc64le'))
self.add_target('ppc64', Target('ppc64'))
# Add IBM architectures
self.add_target('power7', Target('power7'))
self.add_target('power8', Target('power8'))
self.add_target('power8le', Target('power8le'))
self.add_target('power9', Target('power9'))
self.add_target('power9le', Target('power9le'))
self.default = get_cpu_name()
self.default = cpu.host().name
self.front_end = self.default
self.back_end = self.default
if self.default not in self.targets:
self.add_target(self.default, Target(self.default))
mac_os = MacOs()
self.default_os = str(mac_os)

View file

@ -6,7 +6,8 @@
import platform
from spack.architecture import Platform, Target
from spack.operating_systems.linux_distro import LinuxDistro
from llnl.util.cpu_name import get_cpu_name
import llnl.util.cpu as cpu
class Linux(Platform):
priority = 90
@ -14,41 +15,14 @@ class Linux(Platform):
def __init__(self):
super(Linux, self).__init__('linux')
# Add "basic" architectures
self.add_target('x86_64', Target('x86_64'))
self.add_target('ppc64le', Target('ppc64le'))
self.add_target('ppc64', Target('ppc64'))
# Add Intel architectures
self.add_target('haswell', Target('haswell'))
self.add_target('broadwell', Target('broadwell'))
self.add_target('ivybridge', Target('ivybridge'))
self.add_target('sandybridge', Target('sandybridge'))
self.add_target('knl', Target('knl'))
# Add IBM architectures
self.add_target('power7', Target('power7'))
self.add_target('power8', Target('power8'))
self.add_target('power8le', Target('power8le'))
self.add_target('power9', Target('power9'))
self.add_target('power9le', Target('power9le'))
# Eternal TODO: Add more architectures as needed.
for name in cpu.targets:
self.add_target(name, Target(name))
# Get specific default
self.default = get_cpu_name()
self.default = cpu.host().name
self.front_end = self.default
self.back_end = self.default
if not self.default:
# Fall back on more general name.
# This will likely fall in "basic" architectures list
self.default = platform.machine()
self.front_end = self.default
self.back_end = self.default
if self.default not in self.targets:
self.add_target(self.default, Target(self.default))
linux_dist = LinuxDistro()
self.default_os = str(linux_dist)
self.front_os = self.default_os

View file

@ -9,7 +9,7 @@
class Test(Platform):
priority = 1000000
front_end = 'x86_32'
front_end = 'x86'
back_end = 'x86_64'
default = 'x86_64'

View file

@ -28,6 +28,12 @@
# version strings
'items': {'anyOf': [{'type': 'string'},
{'type': 'number'}]}},
'target': {
'type': 'array',
'default': [],
# target names
'items': {'type': 'string'},
},
'compiler': {
'type': 'array',
'default': [],

View file

@ -85,10 +85,9 @@
import os
import re
import six
from operator import attrgetter
from six import StringIO
from six import string_types
from six import iteritems
from llnl.util.filesystem import find_headers, find_libraries, is_exe
from llnl.util.lang import key_ordering, HashableMap, ObjectWrapper, dedupe
@ -216,37 +215,47 @@ def __call__(self, match):
@key_ordering
class ArchSpec(object):
""" The ArchSpec class represents an abstract architecture specification
that a package should be built with. At its core, each ArchSpec is
comprised of three elements: a platform (e.g. Linux), an OS (e.g.
RHEL6), and a target (e.g. x86_64).
"""
def __init__(self, spec_or_platform_tuple=(None, None, None)):
""" Architecture specification a package should be built with.
# TODO: Formalize the specifications for architectures and then use
# the appropriate parser here to read these specifications.
def __init__(self, *args):
to_attr_string = lambda s: str(s) if s and s != "None" else None
Each ArchSpec is comprised of three elements: a platform (e.g. Linux),
an OS (e.g. RHEL6), and a target (e.g. x86_64).
self.platform, self.os, self.target = (None, None, None)
Args:
spec_or_platform_tuple (ArchSpec or str or tuple): if an ArchSpec
is passed it will be duplicated into the new instance.
Otherwise information on platform, OS and target should be
passed in either as a spec string or as a tuple.
"""
# If another instance of ArchSpec was passed, duplicate it
if isinstance(spec_or_platform_tuple, ArchSpec):
self._dup(spec_or_platform_tuple)
return
if len(args) == 1:
spec_like = args[0]
if isinstance(spec_like, ArchSpec):
self._dup(spec_like)
elif isinstance(spec_like, string_types):
spec_fields = spec_like.split("-")
# If the argument to __init__ is a spec string, parse it
# and construct an ArchSpec
def _string_or_none(s):
if s and s != 'None':
return str(s)
return None
if len(spec_fields) == 3:
self.platform, self.os, self.target = tuple(
to_attr_string(f) for f in spec_fields)
else:
raise ValueError("%s is an invalid arch spec" % spec_like)
elif len(args) == 3:
self.platform = to_attr_string(args[0])
self.os = to_attr_string(args[1])
self.target = to_attr_string(args[2])
elif len(args) != 0:
raise TypeError("Can't make arch spec from %s" % args)
if isinstance(spec_or_platform_tuple, six.string_types):
spec_fields = spec_or_platform_tuple.split("-")
msg = "invalid arch spec [{0}]"
assert len(spec_fields) == 3, msg.format(spec_or_platform_tuple)
platform, operating_system, target = spec_fields
platform_tuple = _string_or_none(platform),\
_string_or_none(operating_system), target
if isinstance(spec_or_platform_tuple, tuple):
platform, operating_system, target = spec_or_platform_tuple
platform_tuple = _string_or_none(platform), \
_string_or_none(operating_system), target
msg = "invalid arch spec tuple [{0}]"
assert len(platform_tuple) == 3, msg.format(platform_tuple)
self.platform, self.os, self.target = platform_tuple
def _autospec(self, spec_like):
if isinstance(spec_like, ArchSpec):
@ -254,7 +263,7 @@ def _autospec(self, spec_like):
return ArchSpec(spec_like)
def _cmp_key(self):
return (self.platform, self.os, self.target)
return self.platform, self.os, self.target
def _dup(self, other):
self.platform = other.platform
@ -263,29 +272,29 @@ def _dup(self, other):
@property
def platform(self):
"""The platform of the architecture."""
return self._platform
@platform.setter
def platform(self, value):
""" The platform of the architecture spec will be verified as a
supported Spack platform before it's set to ensure all specs
refer to valid platforms.
"""
# The platform of the architecture spec will be verified as a
# supported Spack platform before it's set to ensure all specs
# refer to valid platforms.
value = str(value) if value is not None else None
self._platform = value
@property
def os(self):
"""The OS of this ArchSpec."""
return self._os
@os.setter
def os(self, value):
""" The OS of the architecture spec will update the platform field
if the OS is set to one of the reserved OS types so that the
default OS type can be resolved. Since the reserved OS
information is only available for the host machine, the platform
will assumed to be the host machine's platform.
"""
# The OS of the architecture spec will update the platform field
# if the OS is set to one of the reserved OS types so that the
# default OS type can be resolved. Since the reserved OS
# information is only available for the host machine, the platform
# will assumed to be the host machine's platform.
value = str(value) if value is not None else None
if value in spack.architecture.Platform.reserved_oss:
@ -305,19 +314,27 @@ def os(self, value):
@property
def target(self):
"""The target of the architecture."""
return self._target
@target.setter
def target(self, value):
""" The target of the architecture spec will update the platform field
if the target is set to one of the reserved target types so that
the default target type can be resolved. Since the reserved target
information is only available for the host machine, the platform
will assumed to be the host machine's platform.
"""
value = str(value) if value is not None else None
# The target of the architecture spec will update the platform field
# if the target is set to one of the reserved target types so that
# the default target type can be resolved. Since the reserved target
# information is only available for the host machine, the platform
# will assumed to be the host machine's platform.
if value in spack.architecture.Platform.reserved_targets:
def target_or_none(t):
if isinstance(t, spack.architecture.Target):
return t
if t and t != 'None':
return spack.architecture.Target(t)
return None
value = target_or_none(value)
if str(value) in spack.architecture.Platform.reserved_targets:
curr_platform = str(spack.architecture.platform())
self.platform = self.platform or curr_platform
@ -328,25 +345,76 @@ def target(self, value):
(value, self.platform, curr_platform))
spec_platform = spack.architecture.get_platform(self.platform)
value = str(spec_platform.target(value))
value = spec_platform.target(value)
self._target = value
def satisfies(self, other, strict=False):
other = self._autospec(other)
sdict, odict = self.to_cmp_dict(), other.to_cmp_dict()
"""Predicate to check if this spec satisfies a constraint.
if strict or self.concrete:
return all(getattr(self, attr) == getattr(other, attr)
for attr in odict if odict[attr])
else:
return all(getattr(self, attr) == getattr(other, attr)
for attr in odict if sdict[attr] and odict[attr])
Args:
other (ArchSpec or str): constraint on the current instance
strict (bool): if ``False`` the function checks if the current
instance *might* eventually satisfy the constraint. If
``True`` it check if the constraint is satisfied right now.
Returns:
True if the constraint is satisfied, False otherwise.
"""
other = self._autospec(other)
# Check platform and os
for attribute in ('platform', 'os'):
other_attribute = getattr(other, attribute)
self_attribute = getattr(self, attribute)
if strict or self.concrete:
if other_attribute and self_attribute != other_attribute:
return False
else:
if other_attribute and self_attribute and \
self_attribute != other_attribute:
return False
# Check target
return self._satisfies_target(other.target, strict=strict)
def _satisfies_target(self, other_target, strict):
self_target = self.target
need_to_check = bool(other_target) if strict or self.concrete \
else bool(other_target and self_target)
# If there's no need to check we are fine
if not need_to_check:
return True
for target_range in str(other_target).split(','):
t_min, sep, t_max = target_range.partition(':')
# Checking against a single specific target
if not sep and self_target == t_min:
return True
min_ok = self_target.microarchitecture >= t_min if t_min else True
max_ok = self_target.microarchitecture <= t_max if t_max else True
if min_ok and max_ok:
return True
return False
def constrain(self, other):
""" Projects all architecture fields that are specified in the given
spec onto the instance spec if they're missing from the instance
spec. This will only work if the two specs are compatible.
"""Projects all architecture fields that are specified in the given
spec onto the instance spec if they're missing from the instance
spec.
This will only work if the two specs are compatible.
Args:
other (ArchSpec or str): constraints to be added
Returns:
True if the current instance was constrained, False otherwise.
"""
other = self._autospec(other)
@ -354,8 +422,8 @@ def constrain(self, other):
raise UnsatisfiableArchitectureSpecError(self, other)
constrained = False
for attr, svalue in iteritems(self.to_cmp_dict()):
ovalue = getattr(other, attr)
for attr in ('platform', 'os', 'target'):
svalue, ovalue = getattr(self, attr), getattr(other, attr)
if svalue is None and ovalue is not None:
setattr(self, attr, ovalue)
constrained = True
@ -363,26 +431,22 @@ def constrain(self, other):
return constrained
def copy(self):
"""Copy the current instance and returns the clone."""
clone = ArchSpec.__new__(ArchSpec)
clone._dup(self)
return clone
@property
def concrete(self):
return all(v for k, v in iteritems(self.to_cmp_dict()))
def to_cmp_dict(self):
"""Returns a dictionary that can be used for field comparison."""
return dict([
('platform', self.platform),
('os', self.os),
('target', self.target)])
"""True if the spec is concrete, False otherwise"""
# return all(v for k, v in six.iteritems(self.to_cmp_dict()))
return self.platform and self.os and self.target
def to_dict(self):
d = syaml_dict([
('platform', self.platform),
('platform_os', self.os),
('target', self.target)])
('target', self.target.to_dict_or_value())])
return syaml_dict([('arch', d)])
@staticmethod
@ -400,22 +464,26 @@ def from_dict(d):
"""
if not isinstance(d['arch'], dict):
return ArchSpec('spack09', 'unknown', d['arch'])
return ArchSpec(('spack09', 'unknown', d['arch']))
d = d['arch']
if 'platform_os' in d:
return ArchSpec(d['platform'], d['platform_os'], d['target'])
else:
return ArchSpec(d['platform'], d['os'], d['target'])
operating_system = d.get('platform_os', None) or d['os']
target = spack.architecture.Target.from_dict_or_value(d['target'])
return ArchSpec((d['platform'], operating_system, target))
def __str__(self):
return "%s-%s-%s" % (self.platform, self.os, self.target)
def __repr__(self):
# TODO: this needs to be changed (repr is meant to return valid
# TODO: Python code to return an instance equivalent to the current
# TODO: one).
return str(self)
def __contains__(self, string):
return string in str(self)
return string in str(self) or string in self.target
@key_ordering
@ -430,7 +498,7 @@ def __init__(self, *args):
arg = args[0]
# If there is one argument, it's either another CompilerSpec
# to copy or a string to parse
if isinstance(arg, string_types):
if isinstance(arg, six.string_types):
c = SpecParser().parse_compiler(arg)
self.name = c.name
self.versions = c.versions
@ -618,7 +686,7 @@ def copy(self):
return clone
def _cmp_key(self):
return tuple((k, tuple(v)) for k, v in sorted(iteritems(self)))
return tuple((k, tuple(v)) for k, v in sorted(six.iteritems(self)))
def __str__(self):
sorted_keys = [k for k in sorted(self.keys()) if self[k] != []]
@ -945,7 +1013,7 @@ def __init__(self, spec_like=None,
self.external_module = external_module
self._full_hash = full_hash
if isinstance(spec_like, string_types):
if isinstance(spec_like, six.string_types):
spec_list = SpecParser(self).parse(spec_like)
if len(spec_list) > 1:
raise ValueError("More than one spec in string: " + spec_like)
@ -1032,9 +1100,9 @@ def _set_architecture(self, **kwargs):
if not self.architecture:
new_vals = tuple(kwargs.get(arg, None) for arg in arch_attrs)
self.architecture = ArchSpec(*new_vals)
self.architecture = ArchSpec(new_vals)
else:
new_attrvals = [(a, v) for a, v in iteritems(kwargs)
new_attrvals = [(a, v) for a, v in six.iteritems(kwargs)
if a in arch_attrs]
for new_attr, new_value in new_attrvals:
if getattr(self.architecture, new_attr):
@ -1186,7 +1254,7 @@ def traverse_edges(self, visited=None, d=0, deptype='all',
# get initial values for kwargs
depth = kwargs.get('depth', False)
key_fun = kwargs.get('key', id)
if isinstance(key_fun, string_types):
if isinstance(key_fun, six.string_types):
key_fun = attrgetter(key_fun)
yield_root = kwargs.get('root', True)
cover = kwargs.get('cover', 'nodes')
@ -1659,7 +1727,7 @@ def read_yaml_dep_specs(dependency_dict):
formats so that reindex will work on old specs/databases.
"""
for dep_name, elt in dependency_dict.items():
if isinstance(elt, string_types):
if isinstance(elt, six.string_types):
# original format, elt is just the dependency hash.
dag_hash, deptypes = elt, ['build', 'link']
elif isinstance(elt, tuple):
@ -1813,7 +1881,7 @@ def spec_and_dependency_types(s):
# Recurse on dependencies
for s, s_dependencies in dep_like.items():
if isinstance(s, string_types):
if isinstance(s, six.string_types):
dag_node, dependency_types = name_and_dependency_types(s)
else:
dag_node, dependency_types = spec_and_dependency_types(s)
@ -1884,7 +1952,7 @@ def from_json(stream):
tty.debug(e)
raise sjson.SpackJSONError("error parsing JSON spec:", str(e))
def _concretize_helper(self, presets=None, visited=None):
def _concretize_helper(self, concretizer, presets=None, visited=None):
"""Recursive helper function for concretize().
This concretizes everything bottom-up. As things are
concretized, they're added to the presets, and ancestors
@ -1906,8 +1974,9 @@ def _concretize_helper(self, presets=None, visited=None):
# Concretize deps first -- this is a bottom-up process.
for name in sorted(self._dependencies.keys()):
changed |= self._dependencies[
name].spec._concretize_helper(presets, visited)
changed |= self._dependencies[name].spec._concretize_helper(
concretizer, presets, visited
)
if self.name in presets:
changed |= self.constrain(presets[self.name])
@ -1916,11 +1985,10 @@ def _concretize_helper(self, presets=None, visited=None):
# to presets below, their constraints will all be merged, but we'll
# still need to select a concrete package later.
if not self.virtual:
import spack.concretize
concretizer = spack.concretize.concretizer
changed |= any(
(concretizer.concretize_architecture(self),
concretizer.concretize_compiler(self),
concretizer.adjust_target(self),
# flags must be concretized after compiler
concretizer.concretize_compiler_flags(self),
concretizer.concretize_version(self),
@ -1945,7 +2013,7 @@ def _replace_with(self, concrete):
if concrete.name not in dependent._dependencies:
dependent._add_dependency(concrete, deptypes)
def _expand_virtual_packages(self):
def _expand_virtual_packages(self, concretizer):
"""Find virtual packages in this spec, replace them with providers,
and normalize again to include the provider's (potentially virtual)
dependencies. Repeat until there are no virtual deps.
@ -1985,8 +2053,6 @@ def _expand_virtual_packages(self):
if not replacement:
# Get a list of possible replacements in order of
# preference.
import spack.concretize
concretizer = spack.concretize.concretizer
candidates = concretizer.choose_virtual_or_external(spec)
# Try the replacements in order, skipping any that cause
@ -2075,12 +2141,13 @@ def concretize(self, tests=False):
force = False
user_spec_deps = self.flat_dependencies(copy=False)
import spack.concretize
concretizer = spack.concretize.Concretizer(self.copy())
while changed:
changes = (self.normalize(force, tests=tests,
user_spec_deps=user_spec_deps),
self._expand_virtual_packages(),
self._concretize_helper())
self._expand_virtual_packages(concretizer),
self._concretize_helper(concretizer))
changed = any(changes)
force = True
@ -2190,6 +2257,10 @@ def concretize(self, tests=False):
if matches:
raise ConflictsInSpecError(self, matches)
# Check if we can produce an optimized binary (will throw if
# there are declared inconsistencies)
self.architecture.target.optimization_flags(self.compiler)
def _mark_concrete(self, value=True):
"""Mark this spec and its dependencies as concrete.
@ -3283,7 +3354,7 @@ def format(self, format_string=default_format, **kwargs):
color = kwargs.get('color', False)
transform = kwargs.get('transform', {})
out = StringIO()
out = six.StringIO()
def write(s, c=None):
f = cescape(s)
@ -3515,7 +3586,7 @@ def old_format(self, format_string='$_$@$%@+$+$=', **kwargs):
(k.upper(), v) for k, v in kwargs.get('transform', {}).items())
length = len(format_string)
out = StringIO()
out = six.StringIO()
named = escape = compiler = False
named_str = fmt = ''
@ -3790,6 +3861,20 @@ def tree(self, **kwargs):
def __repr__(self):
return str(self)
@property
def platform(self):
return self.architecture.platform
@property
def os(self):
return self.architecture.os
@property
def target(self):
# This property returns the underlying microarchitecture object
# to give to the attribute the appropriate comparison semantic
return self.architecture.target.microarchitecture
class LazySpecCache(collections.defaultdict):
"""Cache for Specs that uses a spec_like as key, and computes lazily

View file

@ -7,10 +7,11 @@
""" Test checks if the architecture class is created correctly and also that
the functions are looking for the correct architecture name
"""
import itertools
import os
import platform as py_platform
import pytest
import spack.architecture
from spack.spec import Spec
from spack.platforms.cray import Cray
@ -82,7 +83,7 @@ def test_user_front_end_input(config):
"""
platform = spack.architecture.platform()
frontend_os = str(platform.operating_system('frontend'))
frontend_target = str(platform.target('frontend'))
frontend_target = platform.target('frontend')
frontend_spec = Spec('libelf os=frontend target=frontend')
frontend_spec.concretize()
@ -97,7 +98,7 @@ def test_user_back_end_input(config):
"""
platform = spack.architecture.platform()
backend_os = str(platform.operating_system("backend"))
backend_target = str(platform.target("backend"))
backend_target = platform.target("backend")
backend_spec = Spec("libelf os=backend target=backend")
backend_spec.concretize()
@ -109,7 +110,7 @@ def test_user_back_end_input(config):
def test_user_defaults(config):
platform = spack.architecture.platform()
default_os = str(platform.operating_system("default_os"))
default_target = str(platform.target("default_target"))
default_target = platform.target("default_target")
default_spec = Spec("libelf") # default is no args
default_spec.concretize()
@ -118,29 +119,20 @@ def test_user_defaults(config):
assert default_target == default_spec.architecture.target
def test_user_input_combination(config):
@pytest.mark.parametrize('operating_system', [
x for x in spack.architecture.platform().operating_sys
] + ["fe", "be", "frontend", "backend"])
@pytest.mark.parametrize('target', [
x for x in spack.architecture.platform().targets
] + ["fe", "be", "frontend", "backend"])
def test_user_input_combination(config, operating_system, target):
platform = spack.architecture.platform()
os_list = list(platform.operating_sys.keys())
target_list = list(platform.targets.keys())
additional = ["fe", "be", "frontend", "backend"]
os_list.extend(additional)
target_list.extend(additional)
combinations = itertools.product(os_list, target_list)
results = []
for arch in combinations:
o, t = arch
spec = Spec("libelf os=%s target=%s" % (o, t))
spec.concretize()
results.append(
spec.architecture.os == str(platform.operating_system(o))
)
results.append(
spec.architecture.target == str(platform.target(t))
)
res = all(results)
assert res
spec = Spec("libelf os=%s target=%s" % (operating_system, target))
spec.concretize()
assert spec.architecture.os == str(
platform.operating_system(operating_system)
)
assert spec.architecture.target == platform.target(target)
def test_operating_system_conversion_to_dict():
@ -148,3 +140,32 @@ def test_operating_system_conversion_to_dict():
assert operating_system.to_dict() == {
'name': 'os', 'version': '1.0'
}
@pytest.mark.parametrize('cpu_flag,target_name', [
# Test that specific flags can be used in queries
('ssse3', 'haswell'),
('popcnt', 'nehalem'),
('avx512f', 'skylake_avx512'),
('avx512ifma', 'icelake'),
# Test that proxy flags can be used in queries too
('sse3', 'nehalem'),
('avx512', 'skylake_avx512'),
('avx512', 'icelake'),
])
def test_target_container_semantic(cpu_flag, target_name):
target = spack.architecture.Target(target_name)
assert cpu_flag in target
@pytest.mark.parametrize('item,architecture_str', [
# We can search the architecture string representation
('linux', 'linux-ubuntu18.04-haswell'),
('ubuntu', 'linux-ubuntu18.04-haswell'),
('haswell', 'linux-ubuntu18.04-haswell'),
# We can also search flags of the target,
('avx512', 'linux-ubuntu18.04-icelake'),
])
def test_arch_spec_container_semantic(item, architecture_str):
architecture = spack.spec.ArchSpec(architecture_str)
assert item in architecture

View file

@ -44,3 +44,7 @@ def test_arch_target():
arch('--target')
arch('-f', '-t')
arch('-b', '-t')
def test_display_targets():
arch('--known-targets')

View file

@ -294,3 +294,9 @@ def test_find_no_sections(database, config):
output = find("--no-groups")
assert "-----------" not in output
assert "==>" not in output
@pytest.mark.db
def test_find_command_basic_usage(database):
output = find()
assert 'mpileaks' in output

View file

@ -70,7 +70,7 @@ def test_multiple_conflicting_compiler_definitions(mutable_config):
compiler_config[0]['compiler']['paths']['f77'] = 'f77'
mutable_config.update_config('compilers', compiler_config)
arch_spec = spack.spec.ArchSpec('test', 'test', 'test')
arch_spec = spack.spec.ArchSpec(('test', 'test', 'test'))
cspec = compiler_config[0]['compiler']['spec']
cmp = compilers.compiler_for_spec(cspec, arch_spec)
assert cmp.f77 == 'f77'

View file

@ -16,6 +16,7 @@
from spack.version import ver
from spack.test.conftest import MockPackage, MockPackageMultiRepo
import spack.compilers
import spack.platforms.test
def check_spec(abstract, concrete):
@ -81,6 +82,16 @@ def spec(request):
return request.param
@pytest.fixture(params=[
'haswell', 'broadwell', 'skylake', 'icelake'
])
def current_host(request, monkeypatch):
target = llnl.util.cpu.targets[request.param]
monkeypatch.setattr(llnl.util.cpu, 'host', lambda: target)
monkeypatch.setattr(spack.platforms.test.Test, 'default', request.param)
return target
@pytest.mark.usefixtures('config', 'mock_packages')
class TestConcretize(object):
def test_concretize(self, spec):
@ -133,12 +144,12 @@ def test_concretize_with_restricted_virtual(self):
assert concrete['mpich2'].satisfies('mpich2@1.3.1:1.4')
def test_concretize_enable_disable_compiler_existence_check(self):
with spack.concretize.concretizer.enable_compiler_existence_check():
with spack.concretize.enable_compiler_existence_check():
with pytest.raises(
spack.concretize.UnavailableCompilerVersionError):
check_concretize('dttop %gcc@100.100')
with spack.concretize.concretizer.disable_compiler_existence_check():
with spack.concretize.disable_compiler_existence_check():
spec = check_concretize('dttop %gcc@100.100')
assert spec.satisfies('%gcc@100.100')
assert spec['dtlink3'].satisfies('%gcc@100.100')
@ -273,7 +284,7 @@ def test_concretize_two_virtuals_with_dual_provider_and_a_conflict(
def test_no_matching_compiler_specs(self, mock_config):
# only relevant when not building compilers as needed
with spack.concretize.concretizer.enable_compiler_existence_check():
with spack.concretize.enable_compiler_existence_check():
s = Spec('a %gcc@0.0.0')
with pytest.raises(
spack.concretize.UnavailableCompilerVersionError):
@ -571,3 +582,17 @@ def test_noversion_pkg(self, spec):
"""Test concretization failures for no-version packages."""
with pytest.raises(NoValidVersionError, match="no valid versions"):
Spec(spec).concretized()
@pytest.mark.parametrize('spec, best_achievable', [
('mpileaks%gcc@4.8', 'haswell'),
('mpileaks%gcc@5.3.0', 'skylake_avx512')
])
def test_adjusting_default_target_based_on_compiler(
self, spec, best_achievable, current_host
):
best_achievable = llnl.util.cpu.targets[best_achievable]
expected = best_achievable if best_achievable < current_host \
else current_host
with spack.concretize.disable_compiler_existence_check():
s = Spec(spec).concretized()
assert str(s.architecture.target) == str(expected)

View file

@ -22,6 +22,6 @@ lmod:
unset:
- BAR
'platform=test target=x86_32':
'platform=test target=x86':
load:
- 'foo/bar'

View file

@ -17,6 +17,6 @@ tcl:
unset:
- BAR
'platform=test target=x86_32':
'platform=test target=x86':
load:
- 'foo/bar'

View file

@ -16,6 +16,6 @@ tcl:
unset:
- BAR
'platform=test target=x86_32':
'platform=test target=x86':
load:
- 'foo/bar'

View file

@ -0,0 +1,4 @@
processor : 0
cpu : POWER7 (architected), altivec supported
clock : 3720.000000MHz
revision : 2.1 (pvr 003f 0201)

View file

@ -0,0 +1,5 @@
machdep.cpu.vendor: GenuineIntel
machdep.cpu.features: FPU VME DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA CMOV PAT PSE36 CLFSH DS ACPI MMX FXSR SSE SSE2 SS HTT TM PBE SSE3 PCLMULQDQ DTES64 MON DSCPL VMX EST TM2 SSSE3 FMA CX16 TPR PDCM SSE4.1 SSE4.2 x2APIC MOVBE POPCNT AES PCID XSAVE OSXSAVE SEGLIM64 TSCTMR AVX1.0 RDRAND F16C
machdep.cpu.leaf7_features: RDWRFSGS TSC_THREAD_OFFSET SGX BMI1 HLE AVX2 SMEP BMI2 ERMS INVPCID RTM FPU_CSDS MPX RDSEED ADX SMAP CLFSOPT IPT MDCLEAR TSXFA IBRS STIBP L1DF SSBD
machdep.cpu.model: 94
machdep.cpu.brand_string: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz

View file

@ -0,0 +1,59 @@
machdep.cpu.max_basic: 13
machdep.cpu.max_ext: 2147483656
machdep.cpu.vendor: GenuineIntel
machdep.cpu.brand_string: Intel(R) Core(TM) i5-3230M CPU @ 2.60GHz
machdep.cpu.family: 6
machdep.cpu.model: 58
machdep.cpu.extmodel: 3
machdep.cpu.extfamily: 0
machdep.cpu.stepping: 9
machdep.cpu.feature_bits: 9203919201183202303
machdep.cpu.leaf7_feature_bits: 641 0
machdep.cpu.leaf7_feature_bits_edx: 2617246720
machdep.cpu.extfeature_bits: 4967106816
machdep.cpu.signature: 198313
machdep.cpu.brand: 0
machdep.cpu.features: FPU VME DE PSE TSC MSR PAE MCE CX8 APIC SEP MTRR PGE MCA CMOV PAT PSE36 CLFSH DS ACPI MMX FXSR SSE SSE2 SS HTT TM PBE SSE3 PCLMULQDQ DTES64 MON DSCPL VMX EST TM2 SSSE3 CX16 TPR PDCM SSE4.1 SSE4.2 x2APIC POPCNT AES PCID XSAVE OSXSAVE TSCTMR AVX1.0 RDRAND F16C
machdep.cpu.leaf7_features: RDWRFSGS SMEP ERMS MDCLEAR IBRS STIBP L1DF SSBD
machdep.cpu.extfeatures: SYSCALL XD EM64T LAHF RDTSCP TSCI
machdep.cpu.logical_per_package: 16
machdep.cpu.cores_per_package: 8
machdep.cpu.microcode_version: 33
machdep.cpu.processor_flag: 4
machdep.cpu.mwait.linesize_min: 64
machdep.cpu.mwait.linesize_max: 64
machdep.cpu.mwait.extensions: 3
machdep.cpu.mwait.sub_Cstates: 135456
machdep.cpu.thermal.sensor: 1
machdep.cpu.thermal.dynamic_acceleration: 1
machdep.cpu.thermal.invariant_APIC_timer: 1
machdep.cpu.thermal.thresholds: 2
machdep.cpu.thermal.ACNT_MCNT: 1
machdep.cpu.thermal.core_power_limits: 1
machdep.cpu.thermal.fine_grain_clock_mod: 1
machdep.cpu.thermal.package_thermal_intr: 1
machdep.cpu.thermal.hardware_feedback: 0
machdep.cpu.thermal.energy_policy: 0
machdep.cpu.xsave.extended_state: 7 832 832 0
machdep.cpu.xsave.extended_state1: 1 0 0 0
machdep.cpu.arch_perf.version: 3
machdep.cpu.arch_perf.number: 4
machdep.cpu.arch_perf.width: 48
machdep.cpu.arch_perf.events_number: 7
machdep.cpu.arch_perf.events: 0
machdep.cpu.arch_perf.fixed_number: 3
machdep.cpu.arch_perf.fixed_width: 48
machdep.cpu.cache.linesize: 64
machdep.cpu.cache.L2_associativity: 8
machdep.cpu.cache.size: 256
machdep.cpu.tlb.inst.small: 64
machdep.cpu.tlb.inst.large: 8
machdep.cpu.tlb.data.small: 64
machdep.cpu.tlb.data.large: 32
machdep.cpu.tlb.shared: 512
machdep.cpu.address_bits.physical: 36
machdep.cpu.address_bits.virtual: 48
machdep.cpu.core_count: 2
machdep.cpu.thread_count: 4
machdep.cpu.tsc_ccc.numerator: 0
machdep.cpu.tsc_ccc.denominator: 0

View file

@ -0,0 +1,4 @@
processor : 0
cpu : POWER8NVL (raw), altivec supported
clock : 2061.000000MHz
revision : 1.0 (pvr 004c 0100)

View file

@ -0,0 +1,25 @@
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 79
model name : Intel(R) Xeon(R) CPU E5-2690 v4 @ 2.60GHz
stepping : 1
microcode : 0xb00002a
cpu MHz : 2600.000
cache size : 35840 KB
physical id : 0
siblings : 14
core id : 0
cpu cores : 14
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 20
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 invpcid_single intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdt_a rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local ibpb ibrs stibp dtherm arat pln pts spec_ctrl intel_stibp
bogomips : 5188.06
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:

View file

@ -0,0 +1,25 @@
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 63
model name : Intel(R) Xeon(R) CPU E5-2680 v3 @ 2.50GHz
stepping : 2
microcode : 0x3c
cpu MHz : 1757.910
cache size : 30720 KB
physical id : 0
siblings : 12
core id : 0
cpu cores : 12
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 15
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb invpcid_single tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc ibpb ibrs stibp dtherm arat pln pts spec_ctrl intel_stibp
bogomips : 4987.97
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:

View file

@ -0,0 +1,25 @@
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 62
model name : Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz
stepping : 4
microcode : 0x42c
cpu MHz : 1862.554
cache size : 20480 KB
physical id : 0
siblings : 8
core id : 0
cpu cores : 8
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase smep erms xsaveopt ibpb ibrs stibp dtherm ida arat pln pts spec_ctrl intel_stibp
bogomips : 5200.15
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:

View file

@ -0,0 +1,25 @@
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 85
model name : Intel(R) Xeon(R) Gold 6132 CPU @ 2.60GHz
stepping : 4
microcode : 0x200004d
cpu MHz : 2600.000
cache size : 19712 KB
physical id : 0
siblings : 14
core id : 0
cpu cores : 14
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 22
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb cat_l3 cdp_l3 invpcid_single intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local ibpb ibrs stibp dtherm ida arat pln pts spec_ctrl intel_stibp ssbd
bogomips : 5200.00
clflush size : 64
cache_alignment : 64
address sizes : 46 bits physical, 48 bits virtual
power management:

View file

@ -0,0 +1,26 @@
processor : 0
vendor_id : AuthenticAMD
cpu family : 23
model : 1
model name : AMD EPYC 7301 16-Core Processor
stepping : 2
microcode : 0x8001227
cpu MHz : 2200.000
cache size : 512 KB
physical id : 0
siblings : 32
core id : 0
cpu cores : 16
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc art rep_good nopl nonstop_tsc extd_apicid amd_dcm aperfmperf eagerfpu pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_l2 cpb hw_pstate retpoline_amd ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 clzero irperf xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca
bogomips : 4399.40
TLB size : 2560 4K pages
clflush size : 64
cache_alignment : 64
address sizes : 48 bits physical, 48 bits virtual
power management: ts ttp tm hwpstate cpb eff_freq_ro [13] [14]

View file

@ -0,0 +1,26 @@
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 61
model name : Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
stepping : 4
microcode : 0x2d
cpu MHz : 1944.124
cache size : 3072 KB
physical id : 0
siblings : 4
core id : 0
cpu cores : 2
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 20
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap intel_pt xsaveopt dtherm ida arat pln pts md_clear flush_l1d
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds
bogomips : 4389.80
clflush size : 64
cache_alignment : 64
address sizes : 39 bits physical, 48 bits virtual
power management:

View file

@ -0,0 +1,233 @@
# Copyright 2013-2019 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import pytest
import contextlib
import os.path
import jsonschema
import llnl.util.cpu
import llnl.util.cpu.detect
import spack.paths
# This is needed to check that with repr we could create equivalent objects
from llnl.util.cpu import Microarchitecture # noqa
@pytest.fixture(params=[
'linux-ubuntu18.04-broadwell',
'linux-rhel7-broadwell',
'linux-rhel7-skylake_avx512',
'linux-rhel7-ivybridge',
'linux-rhel7-haswell',
'linux-rhel7-zen',
'linux-centos7-power8le',
'darwin-mojave-ivybridge',
'darwin-mojave-broadwell',
'bgq-rhel6-power7'
])
def expected_target(request, monkeypatch):
cpu = llnl.util.cpu
platform, operating_system, target = request.param.split('-')
architecture_family = llnl.util.cpu.targets[target].family
monkeypatch.setattr(
cpu.detect.platform, 'machine', lambda: str(architecture_family)
)
# Monkeypatch for linux
if platform in ('linux', 'bgq'):
monkeypatch.setattr(cpu.detect.platform, 'system', lambda: 'Linux')
@contextlib.contextmanager
def _open(not_used_arg):
filename = os.path.join(
spack.paths.test_path, 'data', 'targets', request.param
)
with open(filename) as f:
yield f
monkeypatch.setattr(cpu.detect, 'open', _open, raising=False)
elif platform == 'darwin':
monkeypatch.setattr(cpu.detect.platform, 'system', lambda: 'Darwin')
filename = os.path.join(
spack.paths.test_path, 'data', 'targets', request.param
)
info = {}
with open(filename) as f:
for line in f:
key, value = line.split(':')
info[key.strip()] = value.strip()
def _check_output(args):
current_key = args[-1]
return info[current_key]
monkeypatch.setattr(cpu.detect, 'check_output', _check_output)
return llnl.util.cpu.targets[target]
@pytest.fixture(params=[x for x in llnl.util.cpu.targets])
def supported_target(request):
return request.param
def test_target_detection(expected_target):
detected_target = llnl.util.cpu.host()
assert detected_target == expected_target
def test_no_dashes_in_target_names(supported_target):
assert '-' not in supported_target
def test_str_conversion(supported_target):
assert supported_target == str(llnl.util.cpu.targets[supported_target])
def test_repr_conversion(supported_target):
target = llnl.util.cpu.targets[supported_target]
assert eval(repr(target)) == target
def test_equality(supported_target):
target = llnl.util.cpu.targets[supported_target]
for name, other_target in llnl.util.cpu.targets.items():
if name == supported_target:
assert other_target == target
else:
assert other_target != target
@pytest.mark.parametrize('operation,expected_result', [
# Test microarchitectures that are ordered with respect to each other
('x86_64 < skylake', True),
('icelake > skylake', True),
('piledriver <= zen', True),
('zen2 >= zen', True),
('zen >= zen', True),
# Test unrelated microarchitectures
('power8 < skylake', False),
('power8 <= skylake', False),
('skylake < power8', False),
('skylake <= power8', False),
# Test microarchitectures of the same family that are not a "subset"
# of each other
('cascadelake > cannonlake', False),
('cascadelake < cannonlake', False),
('cascadelake <= cannonlake', False),
('cascadelake >= cannonlake', False),
('cascadelake == cannonlake', False),
('cascadelake != cannonlake', True)
])
def test_partial_ordering(operation, expected_result):
target, operator, other_target = operation.split()
target = llnl.util.cpu.targets[target]
other_target = llnl.util.cpu.targets[other_target]
code = 'target ' + operator + 'other_target'
assert eval(code) is expected_result
@pytest.mark.parametrize('target_name,expected_family', [
('skylake', 'x86_64'),
('zen', 'x86_64'),
('pentium2', 'x86'),
])
def test_architecture_family(target_name, expected_family):
target = llnl.util.cpu.targets[target_name]
assert str(target.family) == expected_family
@pytest.mark.parametrize('target_name,feature', [
('skylake', 'avx2'),
('icelake', 'avx512f'),
# Test feature aliases
('icelake', 'avx512'),
('skylake', 'sse3'),
('power8', 'altivec'),
('broadwell', 'sse4.1'),
])
def test_features_query(target_name, feature):
target = llnl.util.cpu.targets[target_name]
assert feature in target
@pytest.mark.parametrize('target_name,wrong_feature', [
('skylake', 1),
('bulldozer', llnl.util.cpu.targets['x86_64'])
])
def test_wrong_types_for_features_query(target_name, wrong_feature):
target = llnl.util.cpu.targets[target_name]
with pytest.raises(TypeError, match='only objects of string types'):
assert wrong_feature in target
def test_generic_microarchitecture():
generic_march = llnl.util.cpu.generic_microarchitecture('foo')
assert generic_march.name == 'foo'
assert not generic_march.features
assert not generic_march.ancestors
assert generic_march.vendor == 'generic'
def test_target_json_schema():
# The file microarchitectures.json contains static data i.e. data that is
# not meant to be modified by users directly. It is thus sufficient to
# validate it only once during unit tests.
json_data = llnl.util.cpu.schema.targets_json.data
jsonschema.validate(json_data, llnl.util.cpu.schema.schema)
@pytest.mark.parametrize('target_name,compiler,version,expected_flags', [
('x86_64', 'gcc', '4.9.3', '-march=x86-64 -mtune=x86-64'),
('nocona', 'gcc', '4.9.3', '-march=nocona -mtune=nocona'),
('nehalem', 'gcc', '4.9.3', '-march=nehalem -mtune=nehalem'),
('nehalem', 'gcc', '4.8.5', '-march=corei7 -mtune=corei7'),
('sandybridge', 'gcc', '4.8.5', '-march=corei7-avx -mtune=corei7-avx'),
# Test that an unknown compiler returns an empty string
('sandybridge', 'unknown', '4.8.5', ''),
])
def test_optimization_flags(target_name, compiler, version, expected_flags):
target = llnl.util.cpu.targets[target_name]
flags = target.optimization_flags(compiler, version)
assert flags == expected_flags
@pytest.mark.parametrize('target_name,compiler,version', [
('excavator', 'gcc', '4.8.5')
])
def test_unsupported_optimization_flags(target_name, compiler, version):
target = llnl.util.cpu.targets[target_name]
with pytest.raises(
llnl.util.cpu.UnsupportedMicroarchitecture,
matches='cannot produce optimized binary'
):
target.optimization_flags(compiler, version)
@pytest.mark.parametrize('operation,expected_result', [
# In the tests below we won't convert the right hand side to
# Microarchitecture, so that automatic conversion from a known
# target name will be tested
('cascadelake > cannonlake', False),
('cascadelake < cannonlake', False),
('cascadelake <= cannonlake', False),
('cascadelake >= cannonlake', False),
('cascadelake == cannonlake', False),
('cascadelake != cannonlake', True)
])
def test_automatic_conversion_on_comparisons(operation, expected_result):
target, operator, other_target = operation.split()
target = llnl.util.cpu.targets[target]
code = 'target ' + operator + 'other_target'
assert eval(code) is expected_result

View file

@ -124,7 +124,7 @@ def test_alter_environment(self, modulefile_content, module_configuration):
assert len([x for x in content if 'unsetenv("BAR")' in x]) == 1
content = modulefile_content(
'libdwarf %clang platform=test target=x86_32'
'libdwarf %clang platform=test target=x86'
)
assert len(

View file

@ -112,7 +112,7 @@ def test_alter_environment(self, modulefile_content, module_configuration):
assert len([x for x in content if 'setenv MPILEAKS_ROOT' in x]) == 1
content = modulefile_content(
'libdwarf %clang platform=test target=x86_32'
'libdwarf %clang platform=test target=x86'
)
assert len([x for x in content

View file

@ -183,13 +183,13 @@ def test_satisfies_architecture(self):
check_unsatisfiable(
'foo platform=linux',
'platform=test os=redhat6 target=x86_32')
'platform=test os=redhat6 target=x86')
check_unsatisfiable(
'foo os=redhat6',
'platform=test os=debian6 target=x86_64')
check_unsatisfiable(
'foo target=x86_64',
'platform=test os=redhat6 target=x86_32')
'platform=test os=redhat6 target=x86')
check_satisfies(
'foo arch=test-None-None',
@ -217,8 +217,8 @@ def test_satisfies_architecture(self):
'foo platform=test target=default_target os=default_os',
'platform=test os=default_os')
check_unsatisfiable(
'foo platform=test target=x86_32 os=redhat6',
'platform=linux target=x86_32 os=redhat6')
'foo platform=test target=x86 os=redhat6',
'platform=linux target=x86 os=redhat6')
def test_satisfies_dependencies(self):
check_satisfies('mpileaks^mpich', '^mpich')
@ -944,3 +944,35 @@ def test_abstract_spec_prefix_error(self):
with pytest.raises(SpecError):
spec.prefix
def test_forwarding_of_architecture_attributes(self):
spec = Spec('libelf').concretized()
# Check that we can still access each member through
# the architecture attribute
assert 'test' in spec.architecture
assert 'debian' in spec.architecture
assert 'x86_64' in spec.architecture
# Check that we forward the platform and os attribute correctly
assert spec.platform == 'test'
assert spec.os == 'debian6'
# Check that the target is also forwarded correctly and supports
# all the operators we expect
assert spec.target == 'x86_64'
assert spec.target.family == 'x86_64'
assert 'avx512' not in spec.target
assert spec.target < 'broadwell'
@pytest.mark.parametrize('spec,constraint,expected_result', [
('libelf target=haswell', 'target=broadwell', False),
('libelf target=haswell', 'target=haswell', True),
('libelf target=haswell', 'target=x86_64:', True),
('libelf target=haswell', 'target=:haswell', True),
('libelf target=haswell', 'target=icelake,:nocona', False),
('libelf target=haswell', 'target=haswell,:nocona', True),
])
def test_target_constraints(self, spec, constraint, expected_result):
s = Spec(spec)
assert s.satisfies(constraint) is expected_result

View file

@ -105,6 +105,7 @@ def check_lex(self, tokens, spec):
"""Check that the provided spec parses to the provided token list."""
spec = shlex.split(str(spec))
lex_output = sp.SpecLexer().lex(spec)
assert len(tokens) == len(lex_output), "unexpected number of tokens"
for tok, spec_tok in zip(tokens, lex_output):
if tok.type == sp.ID or tok.type == sp.VAL:
assert tok == spec_tok
@ -184,7 +185,7 @@ def test_full_specs(self):
self.check_parse(
"mvapich_foo"
" ^_openmpi@1.2:1.4,1.6%intel@12.1 debug=2 ~qt_4"
" ^stackwalker@8.1_1e arch=test-redhat6-x86_32")
" ^stackwalker@8.1_1e arch=test-redhat6-x86")
def test_canonicalize(self):
self.check_parse(
@ -749,3 +750,16 @@ def test_kv_with_spaces(self):
"mvapich_foo debug= 4 "
"^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 "
"^ stackwalker @ 8.1_1e")
@pytest.mark.parametrize('expected_tokens,spec_string', [
([Token(sp.ID, 'target'),
Token(sp.EQ, '='),
Token(sp.VAL, 'broadwell')],
'target=broadwell'),
([Token(sp.ID, 'target'),
Token(sp.EQ, '='),
Token(sp.VAL, ':broadwell,icelake')],
'target=:broadwell,icelake')
])
def test_target_tokenization(self, expected_tokens, spec_string):
self.check_lex(expected_tokens, spec_string)