Skip to content

Allow optarch values to be partial maps including vector extensions #3797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
10 changes: 6 additions & 4 deletions easybuild/toolchains/compiler/inteliccifort.py
Original file line number Diff line number Diff line change
@@ -74,13 +74,15 @@ class IntelIccIfort(Compiler):

# used when 'optarch' toolchain option is enabled (and --optarch is not specified)
COMPILER_OPTIMAL_ARCHITECTURE_OPTION = {
(systemtools.X86_64, systemtools.AMD): '-xHost',
(systemtools.X86_64, systemtools.INTEL): '-xHost',
systemtools.X86_64: '-xHost',
# Intel compilers don't auto-detect AMD features and default to SSE2
(systemtools.X86_64, systemtools.AMD, systemtools.SSSE3): '-mssse3',
(systemtools.X86_64, systemtools.AMD, systemtools.SSE4_2): '-msse4.2',
(systemtools.X86_64, systemtools.AMD, systemtools.AVX2): '-march=core-avx2',
}
# used with --optarch=GENERIC
COMPILER_GENERIC_OPTION = {
(systemtools.X86_64, systemtools.AMD): '-xSSE2',
(systemtools.X86_64, systemtools.INTEL): '-xSSE2',
systemtools.X86_64: '-xSSE2',
}

COMPILER_CC = 'icc'
36 changes: 6 additions & 30 deletions easybuild/tools/options.py
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@
from collections import OrderedDict

import easybuild.tools.environment as env
from easybuild.base import fancylogger # build_log should always stay there, to ensure EasyBuildLog
from easybuild.base import fancylogger
from easybuild.base.fancylogger import setLogLevel
from easybuild.base.generaloption import GeneralOption
from easybuild.framework.easyblock import MODULE_ONLY_STEPS, EXTRACT_STEP, FETCH_STEP, EasyBlock
@@ -102,7 +102,7 @@
from easybuild.tools.robot import det_robot_path
from easybuild.tools.run import run_shell_cmd
from easybuild.tools.package.utilities import avail_package_naming_schemes
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, OPTARCH_MAP_CHAR, OPTARCH_SEP, Compiler
from easybuild.tools.toolchain.compiler import DEFAULT_OPT_LEVEL, Compiler, parse_optarch_string
from easybuild.tools.toolchain.toolchain import DEFAULT_SEARCH_PATH_CPP_HEADERS, DEFAULT_SEARCH_PATH_LINKER, SEARCH_PATH
from easybuild.tools.toolchain.toolchain import SYSTEM_TOOLCHAIN_NAME
from easybuild.tools.repository.repository import avail_repositories
@@ -1031,34 +1031,10 @@ def postprocess(self):
cleanup_and_exit(self.tmpdir)

def _postprocess_optarch(self):
"""Postprocess --optarch option."""
optarch_parts = self.options.optarch.split(OPTARCH_SEP)

# we expect to find a ':' in every entry in optarch, in case optarch is specified on a per-compiler basis
n_parts = len(optarch_parts)
map_char_cnts = [p.count(OPTARCH_MAP_CHAR) for p in optarch_parts]
if (n_parts > 1 and any(c != 1 for c in map_char_cnts)) or (n_parts == 1 and map_char_cnts[0] > 1):
raise EasyBuildError(
"The optarch option has an incorrect syntax: %s", self.options.optarch,
exit_code=EasyBuildExit.OPTION_ERROR
)
else:
# if there are options for different compilers, we set up a dict
if OPTARCH_MAP_CHAR in optarch_parts[0]:
optarch_dict = {}
for compiler, compiler_opt in [p.split(OPTARCH_MAP_CHAR) for p in optarch_parts]:
if compiler in optarch_dict:
raise EasyBuildError(
"The optarch option contains duplicated entries for compiler %s: %s",
compiler, self.options.optarch, exit_code=EasyBuildExit.OPTION_ERROR
)
else:
optarch_dict[compiler] = compiler_opt
self.options.optarch = optarch_dict
self.log.info("Transforming optarch into a dict: %s", self.options.optarch)
# if optarch is not in mapping format, we do nothing and just keep the string
else:
self.log.info("Keeping optarch raw: %s", self.options.optarch)
"""
Postprocess --optarch option.
"""
self.options.optarch = parse_optarch_string(self.options.optarch, include_compiler=True)

def _postprocess_close_pr_reasons(self):
"""Postprocess --close-pr-reasons options"""
50 changes: 50 additions & 0 deletions easybuild/tools/systemtools.py
Original file line number Diff line number Diff line change
@@ -101,6 +101,16 @@

ARCH_KEY_PREFIX = 'arch='

# Vector extension constants
SSE = 'sse'
SSE2 = 'sse2'
SSSE3 = 'ssse3'
SSE4_1 = 'sse4_1'
SSE4_2 = 'sse4_2'
AVX = 'avx'
AVX2 = 'avx2'
AVX512F = 'avx512f'

# Vendor constants
AMD = 'AMD'
APM = 'Applied Micro'
@@ -135,6 +145,8 @@
CPU_ARCHITECTURES = [AARCH32, AARCH64, POWER, RISCV32, RISCV64, X86_64]
CPU_FAMILIES = [AMD, ARM, INTEL, POWER, POWER_LE, RISCV]
CPU_VENDORS = [AMD, APM, APPLE, ARM, BROADCOM, CAVIUM, DEC, IBM, INTEL, MARVELL, MOTOROLA, NVIDIA, QUALCOMM]
# Vector extensions of CPUs in ascending order (later => better)
CPU_VECTOR_EXTS = [SSE, SSE2, SSSE3, SSE4_1, SSE4_2, AVX, AVX2, AVX512F]
# ARM implementer IDs (i.e., the hexadeximal keys) taken from ARMv8-A Architecture Reference Manual
# (ARM DDI 0487A.j, Section G6.2.102, Page G6-4493)
VENDOR_IDS = {
@@ -613,6 +625,13 @@ def get_cpu_features():
return cpu_feat


def get_cpu_vector_exts():
"""Get list of (relevant) CPU vector extensions"""
cpu_features = set(get_cpu_features())
# Values of the vector extension constants purposely match the cpu feature value
return [i for i in CPU_VECTOR_EXTS if i in cpu_features]


def get_gpu_info():
"""
Get the GPU info
@@ -1357,6 +1376,37 @@ def pick_dep_version(dep_version):
return result


def pick_opt_arch(options, arch, cpu_family, vector_exts):
"""
Pick the best matching entry from the options dict based on CPU arch and features
:param options: Dictionary mapping tuples of system specs to optarchs.
E.g. (X86_64, AMD, AVX2): 'mavx2'
:param arch: Current CPU arch
:param cpu_family: Current CPU family
:param vector_exts: Vector extensions supported by current CPU
"""
def create_possible_keys():
"""Yield a list of possible keys from most specific to least specific"""
for ext in vector_exts:
yield (arch, cpu_family, ext)
yield (arch, cpu_family)
yield (arch, )
# Also allow single string entry
yield arch
# Default fallback for any arch
yield None

result = None
for key in create_possible_keys():
try:
result = options[key]
break
except KeyError:
pass
return result


def det_pypkg_version(pkg_name, imported_pkg, import_name=None):
"""Determine version of a Python package."""

Loading