diff --git a/ci/ciimage/arch/install.sh b/ci/ciimage/arch/install.sh index 402bb040221e..b252c752b539 100755 --- a/ci/ciimage/arch/install.sh +++ b/ci/ciimage/arch/install.sh @@ -15,7 +15,8 @@ pkgs=( doxygen vulkan-headers vulkan-icd-loader vulkan-validation-layers openssh mercurial gtk-sharp-3 qt5-tools libwmf cmake netcdf-fortran openmpi nasm gnustep-base gettext python-lxml hotdoc rust-bindgen qt6-base qt6-tools qt6-declarative wayland wayland-protocols - intel-oneapi-mkl + blas blas64 cblas cblas64 lapack lapack64 lapacke lapacke64 + intel-oneapi-mkl intel-oneapi-openmp openblas tbb # cuda ) diff --git a/ci/ciimage/fedora/image.json b/ci/ciimage/fedora/image.json index c6fdc9e283d3..cb1e43c0c13f 100644 --- a/ci/ciimage/fedora/image.json +++ b/ci/ciimage/fedora/image.json @@ -3,6 +3,7 @@ "env": { "CI": "1", "SKIP_STATIC_BOOST": "1", - "MESON_CI_JOBNAME": "linux-fedora-gcc" + "MESON_CI_JOBNAME": "linux-fedora-gcc", + "MKL_THREADING_LAYER": "GNU" } } diff --git a/ci/ciimage/fedora/install.sh b/ci/ciimage/fedora/install.sh index 62952dc8160d..ed23f9aa62e0 100755 --- a/ci/ciimage/fedora/install.sh +++ b/ci/ciimage/fedora/install.sh @@ -15,8 +15,9 @@ pkgs=( doxygen vulkan-devel vulkan-validation-layers-devel openssh lksctp-tools-devel objfw mercurial gtk-sharp3-devel libpcap-devel gpgme-devel qt5-qtbase-devel qt5-qttools-devel qt5-linguist qt5-qtbase-private-devel qt6-qtdeclarative-devel qt6-qtbase-devel qt6-qttools-devel qt6-linguist qt6-qtbase-private-devel - libwmf-devel valgrind cmake openmpi-devel nasm gnustep-base-devel gettext-devel ncurses-devel + libwmf-devel valgrind cmake openmpi-devel nasm gnustep-base-devel gettext-devel ncurses-devel hwloc-devel libxml2-devel libxslt-devel libyaml-devel glib2-devel json-glib-devel libgcrypt-devel wayland-devel wayland-protocols-devel + openblas-devel blas-devel lapack-devel intel-oneapi-mkl-devel # HACK: remove npm once we switch back to hotdoc sdist nodejs-npm ) @@ -24,6 +25,19 @@ pkgs=( # Sys update dnf -y upgrade +# Add Intel oneAPI repository for mkl +cat > /etc/yum.repos.d/oneAPI.repo < /etc/portage/package.use/ci dev-lang/rust-bin clippy rustfmt dev-libs/boost python sci-libs/hdf5 cxx + sci-libs/lapack lapacke # slimmed binpkg, nomesa media-libs/libsdl2 -opengl -wayland -alsa -dbus -gles2 -udev -vulkan diff --git a/ci/ciimage/opensuse/install.sh b/ci/ciimage/opensuse/install.sh index 7adb3fdfc980..921c14b9a940 100755 --- a/ci/ciimage/opensuse/install.sh +++ b/ci/ciimage/opensuse/install.sh @@ -19,6 +19,7 @@ pkgs=( boost-devel libboost_date_time-devel libboost_filesystem-devel libboost_locale-devel libboost_headers-devel libboost_test-devel libboost_log-devel libboost_regex-devel libboost_python3-devel libboost_regex-devel + blas-devel cblas-devel lapack-devel lapacke-devel openblas-devel intel-oneapi-mkl-devel # HACK: remove npm once we switch back to hotdoc sdist npm ) @@ -27,8 +28,21 @@ pkgs=( zypper --non-interactive patch --with-update --with-optional zypper --non-interactive update +# Add Intel oneAPI repository for mkl +cat > /etc/zypp/repos.d/oneAPI.repo <<-EOF +[oneAPI] +name=IntelĀ® oneAPI repository +baseurl=https://yum.repos.intel.com/oneapi +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://yum.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB +# use low priority or it will replace openmpi-devel +priority=10000 +EOF + # Install deps -zypper install -y "${pkgs[@]}" +zypper --gpg-auto-import-keys install -y "${pkgs[@]}" # HACK: build hotdoc from git repo since current sdist is broken on modern compilers # change back to 'hotdoc' once it's fixed install_python_packages git+https://github.com/hotdoc/hotdoc diff --git a/ci/ciimage/ubuntu-rolling/install.sh b/ci/ciimage/ubuntu-rolling/install.sh index 3460be48e9f5..637bd70812a1 100755 --- a/ci/ciimage/ubuntu-rolling/install.sh +++ b/ci/ciimage/ubuntu-rolling/install.sh @@ -29,6 +29,8 @@ pkgs=( itstool openjdk-11-jre jq + libblas-dev liblapack-dev liblapacke-dev libblas64-dev liblapack64-dev liblapacke64-dev + libopenblas-dev libopenblas64-dev libmkl-dev ) sed -i '/^Types: deb/s/deb/deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources diff --git a/docs/markdown/Dependencies.md b/docs/markdown/Dependencies.md index 09b74cfd7b83..0127207501fa 100644 --- a/docs/markdown/Dependencies.md +++ b/docs/markdown/Dependencies.md @@ -327,6 +327,152 @@ to what is provided by the C runtime libraries. `method` may be `auto`, `builtin` or `system`. +## BLAS and LAPACK + +*(added 1.11.0)* + +Enables compiling and linking against BLAS and LAPACK libraries. BLAS and +LAPACK are generic APIs, which can be provided by a number of different +implementations. It is possible to request either any implementation that +provides the API, or a specific implementation like OpenBLAS or MKL. +Furthermore, a preferred order may be specified, as well as the default +integer size (LP64/32-bit or ILP64/64-bit) and whether to detect the Fortran +and/or C APIs. + +The common API between all BLAS and LAPACK dependencies uses the `modules` +keyword, with possible values: + +- `'interface: lp64'` (default) or `'interface: ilp64'`: to select the default + integer size +- `'cblas'`: require the dependency to provide CBLAS (in addition to BLAS, + which is always present) +- `'lapack'`: require the dependency to provide LAPACK + +The `get_variable` method on the returned dependency object may be used to +query the symbol suffix for the found library. Different libraries use +different symbol suffixes, and those are needed to link against the library +from C/Fortran code. Those suffixes may be fixed, like is the case for MKL and +Accelerate, or depend on the build configuration of the library, like is the +case for OpenBLAS. + +Usage may look like: + +```meson +dep_mkl = dependency('mkl', modules: ['interface: ilp64', 'cblas', 'lapack']) + +# Or, to select one of multiple BLAS/LAPACK implementations: + +blas = dependency( + ['accelerate', 'flexiblas', 'mkl', 'openblas', 'blas'], + modules: ['interface: ilp64', 'cblas', 'lapack'], +) +blas_symbol_suffix = blas.get_variable('symbol_suffix') +if blas_symbol_suffix != '' + # The define here will be specific to the users code + _args_blas = ['-DBLAS_SYMBOL_SUFFIX=' + blas_symbol_suffix] +endif +blas_dep = declare_dependency(dependencies: [blas], compile_args: _args_blas) + +# Adding the generic BLAS or LAPACK as a fallback, this may improve portability +blas_dep = dependency(['accelerate', 'mkl', 'openblas', 'blas') +lapack_dep = dependency(['accelerate', 'mkl', 'openblas', 'lapack') +``` + +Note that it is not necessary to specify both `'lapack'` and `'blas'` for the +same build target, because LAPACK itself depends on BLAS. + + +### Specific BLAS and LAPACK implementations + +#### OpenBLAS + +The `version` and `interface` keywords may be passed to request the use of a +specific version and interface, correspondingly: + +```meson +openblas_dep = dependency('openblas', + version : '>=0.3.21', + modules: [ + 'interface: ilp64', # can be lp64 or ilp64 + 'cblas', + 'lapack', + ] +) +``` + +If OpenBLAS is installed in a nonstandard location *with* pkg-config files, +you can set `PKG_CONFIG_PATH`. Alternatively, you can specify +`openblas_includedir` and `openblas_librarydir` in your native or cross machine +file, this works also if OpenBLAS is installed *without* pkg-config files: + +```ini +[properties] +openblas_includedir = '/path/to/include_dir' # should contain openblas_config.h +openblas_librarydir = '/path/to/library_dir' +``` + +OpenBLAS build configurations may include different symbol suffixes for ILP64 builds, +differences in library and pkg-config file naming conventions, and with or without +CBLAS or LAPACK. Meson will autodetect common symbol suffix conventions and validate +that, if `'cblas'` or `'lapack'` modules are requested, the found library actually +was built with support for those APIs. + +OpenBLAS can be built with either pthreads or OpenMP threading. Information on +this is not available through Meson. + +#### Intel MKL + +MKL is built in a fixed build configuration per release by Intel, and always +includes both LP64 and ILP64, and CBLAS and LAPACK. MKL provides a number of +options for the threading layer - these can be selected through an MKL-specific +`'threading'` module: + +```meson +mkl_dep = dependency('mkl', + version: '>=2023.2.0', + modules: [ + interface: 'lp64', # options are 'lp64' or 'ilp64' + threading: 'seq', # options are 'seq', 'iomp', 'gomp', 'tbb' + ] +) +``` + +In addition to all combinations of LP64/ILP64, threading, and dynamic/static +libraries, MLK provides a "Single Dynamic Library" (SDL). To select it, use the +`'sdl'` module. The SDL defaults to the LP64 interface and Intel OpenMP +(`'iomp'`) threading, with switching other interface and threading options +being accessible at runtime. When using the `'sdl'` module, either leave out +the interface and threading modules or use values that match the SDL defaults - +anything else will raise an error. + +### Accelerate + +Apple's Accelerate framework provides BLAS and LAPACK. It received a major update +in macOS 13.3, with LAPACK updated from 3.2 to 3.9, ILP64 support added, and +many longstanding bugs fixed. The `accelerate` dependency therefore only supports +this new version, and won't return a result on macOS <13.3 (note: the +corresponding 13.3 XCode SDK must also be installed) or if +`MACOS_DEPLOYMENT_TARGET` is set to a version lower than 13.3. + +```meson +accelerate_dep = dependency('accelerate', modules: [interface: 'lp64']) +``` + +Note that the default, older Accelerate version can still be detected through +Meson's `appleframeworks` detection method: + +```meson +dependency('Accelerate') +# Or: +dependency('appleframeworks', modules : 'Accelerate') +``` +Those methods do not support any of the BLAS/LAPACK-specific modules. + +### Other BLAS and LAPACK libraries + +Specific support for other BLAS/LAPACK libraries is not (yet) included. They +can still be detected via the regular pkg-config or CMake support. + ## Blocks Enable support for Clang's blocks extension. diff --git a/mesonbuild/dependencies/__init__.py b/mesonbuild/dependencies/__init__.py index c45a39f6ddf0..aaa7167ea8e2 100644 --- a/mesonbuild/dependencies/__init__.py +++ b/mesonbuild/dependencies/__init__.py @@ -8,6 +8,7 @@ BuiltinDependency, SystemDependency, get_leaf_external_dependencies) from .detect import find_external_dependency, get_dep_identifier, packages, _packages_accept_language + __all__ = [ 'Dependency', 'InternalDependency', @@ -227,6 +228,13 @@ def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T. 'libssl': 'misc', 'objfw': 'misc', + # From blas_lapack: + 'accelerate': 'blas_lapack', + 'blas': 'blas_lapack', + 'lapack': 'blas_lapack', + 'mkl': 'blas_lapack', + 'openblas': 'blas_lapack', + # From platform: 'appleframeworks': 'platform', diff --git a/mesonbuild/dependencies/blas_lapack.py b/mesonbuild/dependencies/blas_lapack.py new file mode 100644 index 000000000000..277d709dc2d7 --- /dev/null +++ b/mesonbuild/dependencies/blas_lapack.py @@ -0,0 +1,813 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright 2013-2025 The Meson development team + +from __future__ import annotations + +from pathlib import Path +import abc +import os +import platform +import re +import subprocess +import sys +import typing as T + +from .. import mlog +from .. import mesonlib +from ..mesonlib import MachineChoice +from ..options import OptionKey + +from .base import DependencyCandidate, DependencyException, DependencyMethods, SystemDependency +from .cmake import CMakeDependency +from .detect import packages +from .factory import DependencyFactory, factory_methods +from .pkgconfig import PkgConfigDependency + +if T.TYPE_CHECKING: + from .base import DependencyObjectKWs, ExternalDependency + from .factory import DependencyGenerator + from ..environment import Environment + BLASLAPACKMixinBase = ExternalDependency +else: + BLASLAPACKMixinBase = object + + +# See https://gist.github.com/rgommers/e10c7cf3ebd88883458544e535d7e54c for details on +# the different libraries, symbol mangling conventions, links to other +# detection implementations and distro recipes, etc. + + +def check_blas_machine_file(name: str, props: dict) -> T.Tuple[bool, T.List[str]]: + # TBD: do we need to support multiple extra dirs? + incdir = props.get(f'{name}_includedir') + assert incdir is None or isinstance(incdir, str) + libdir = props.get(f'{name}_librarydir') + assert libdir is None or isinstance(libdir, str) + + if incdir and libdir: + has_dirs = True + if not Path(incdir).is_absolute() or not Path(libdir).is_absolute(): + raise mesonlib.MesonException('Paths given for openblas_includedir and ' + 'openblas_librarydir in machine file must be absolute') + return has_dirs, [libdir, incdir] + elif incdir or libdir: + raise mesonlib.MesonException('Both openblas_includedir *and* openblas_librarydir ' + 'have to be set in your machine file (one is not enough)') + else: + raise mesonlib.MesonBugException('issue with openblas dependency detection, should not ' + 'be possible to reach this else clause') + return (False, []) + + +class BLASLAPACKMixin(BLASLAPACKMixinBase, metaclass=abc.ABCMeta): + @abc.abstractmethod + def get_symbol_suffix(self) -> str: + raise NotImplementedError + + def parse_modules(self, kwargs: DependencyObjectKWs) -> None: + modules: T.List[str] = kwargs.get('modules', []) + valid_modules = ['interface: lp64', 'interface: ilp64', 'cblas', 'lapack', 'lapacke'] + for module in modules: + if module not in valid_modules: + raise mesonlib.MesonException(f'Unknown modules argument: {module}') + + interface = [s for s in modules if s.startswith('interface:')] + if interface: + if len(interface) > 1: + raise mesonlib.MesonException(f'Only one interface must be specified, found: {interface}') + self.interface = interface[0].split(' ', 1)[1] + else: + self.interface = 'lp64' + + self.needs_cblas = 'cblas' in modules + self.needs_lapack = 'lapack' in modules + self.needs_lapacke = 'lapacke' in modules + + def check_symbols(self, compile_args: list[str], suffix: T.Optional[str] = None, check_cblas: bool = True, + check_lapacke: bool = True, lapack_only: bool = False) -> bool: + # verify that we've found the right LP64/ILP64 interface + symbols = [] + if not lapack_only: + symbols += ['dgemm_'] + if check_cblas and self.needs_cblas: + symbols += ['cblas_dgemm'] + if self.needs_lapack: + symbols += ['zungqr_'] + if check_lapacke and self.needs_lapacke: + symbols += ['LAPACKE_zungqr'] + + if suffix is None: + suffix = self.get_symbol_suffix() + + prototypes = "".join(f"void {symbol}{suffix}();\n" for symbol in symbols) + calls = " ".join(f"{symbol}{suffix}();\n" for symbol in symbols) + code = (f"{prototypes}" + "int main(int argc, const char *argv[])\n" + "{\n" + f" {calls}" + " return 0;\n" + "}" + ) + code = '''#ifdef __cplusplus + extern "C" { + #endif + ''' + code + ''' + #ifdef __cplusplus + } + #endif + ''' + + return self.clib_compiler.links(code, extra_args=compile_args)[0] + + def get_variable(self, **kwargs: T.Any) -> str: + varname = kwargs['pkgconfig'] + if varname == 'interface': + return self.interface + elif varname == 'symbol_suffix': + return self.get_symbol_suffix() + return super().get_variable(**kwargs) + + +class OpenBLASMixin(BLASLAPACKMixin): + def get_symbol_suffix(self) -> str: + return '' if self.interface == 'lp64' else self._ilp64_suffix + + def probe_symbols(self, compile_args: list[str]) -> bool: + """There are two common ways of building ILP64 BLAS, check which one we're dealing with""" + if self.interface == 'lp64': + return self.check_symbols(compile_args) + + if self.check_symbols(compile_args, '64_'): + self._ilp64_suffix = '64_' + elif self.check_symbols(compile_args, ''): + self._ilp64_suffix = '' + else: + return False + return True + + +class OpenBLASSystemDependency(OpenBLASMixin, SystemDependency): + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs) -> None: + super().__init__(name, environment, kwargs) + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + + # First, look for paths specified in a machine file + props = self.env.properties[self.for_machine].properties + if any(x in props for x in ['openblas_includedir', 'openblas_librarydir']): + self.detect_openblas_machine_file(props) + + # Then look in standard directories by attempting to link + if not self.is_found: + extra_libdirs: T.List[str] = [] + self.detect(extra_libdirs) + + if self.is_found: + self.version = self.detect_openblas_version() + + def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[T.List[str]] = None) -> None: + if lib_dirs is None: + lib_dirs = [] + if inc_dirs is None: + inc_dirs = [] + + if self.interface == 'lp64': + libnames = ['openblas'] + elif self.interface == 'ilp64': + libnames = ['openblas64', 'openblas_ilp64', 'openblas'] + + for libname in libnames: + link_arg = self.clib_compiler.find_library(libname, lib_dirs) + if link_arg: + incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs] + for hdr in ['openblas_config.h', 'openblas/openblas_config.h']: + found_header, _ = self.clib_compiler.has_header(hdr, '', dependencies=[self], + extra_args=incdir_args) + if found_header: + self._openblas_config_header = hdr + break + + if link_arg and found_header: + if not self.probe_symbols(link_arg): + continue + self.is_found = True + self.link_args += link_arg + self.compile_args += incdir_args + break + + def detect_openblas_machine_file(self, props: dict) -> None: + has_dirs, _dirs = check_blas_machine_file('openblas', props) + if has_dirs: + libdir, incdir = _dirs + self.detect([libdir], [incdir]) + + def detect_openblas_version(self) -> str: + v, _ = self.clib_compiler.get_define('OPENBLAS_VERSION', + f'#include <{self._openblas_config_header}>', + [], [self]) + + m = re.search(r'\d+(?:\.\d+)+', v) + if not m: + mlog.debug('Failed to extract openblas version information') + return None + return m.group(0) + + +class OpenBLASPkgConfigDependency(OpenBLASMixin, PkgConfigDependency): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs) -> None: + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + if self.interface == 'lp64' and name != 'openblas': + # Check only for 'openblas' for LP64 (there are multiple names for ILP64) + self.is_found = False + return None + + super().__init__(name, env, kwargs) + + if self.is_found and not self.probe_symbols(self.link_args): + self.is_found = False + + +class OpenBLASCMakeDependency(OpenBLASMixin, CMakeDependency): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs) -> None: + super().__init__('OpenBLAS', env, kwargs) + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + + if self.interface == 'ilp64': + self.is_found = False + elif self.is_found and not self.probe_symbols(self.link_args): + self.is_found = False + + +class NetlibMixin(BLASLAPACKMixin): + def get_symbol_suffix(self) -> str: + self._ilp64_suffix = '' # Handle `64_` suffix, or custom suffixes? + return '' if self.interface == 'lp64' else self._ilp64_suffix + + def probe_symbols(self, compile_args: list[str], check_cblas: bool = True, check_lapacke: bool = True, + lapack_only: bool = False) -> bool: + """Most ILP64 BLAS builds will not use a suffix, but the new standard will be _64 + (see Reference-LAPACK/lapack#666). Check which one we're dealing with""" + if self.interface == 'lp64': + return self.check_symbols(compile_args, check_cblas=check_cblas, + check_lapacke=check_lapacke, lapack_only=lapack_only) + + if self.check_symbols(compile_args, '_64', check_cblas=check_cblas, + check_lapacke=check_lapacke, lapack_only=lapack_only): + self._ilp64_suffix = '_64' + elif self.check_symbols(compile_args, '', check_cblas=check_cblas, + check_lapacke=check_lapacke, lapack_only=lapack_only): + self._ilp64_suffix = '' + else: + return False + return True + + +class NetlibBLASPkgConfigDependency(NetlibMixin, PkgConfigDependency): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs) -> None: + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + if self.interface == 'lp64' and '64' in name: + # *64* names are used for ILP64 variants + self.is_found = False + return None + + super().__init__(name, env, kwargs) + + if self.is_found: + if self.needs_cblas: + # `name` may be 'blas' or 'blas64'; CBLAS library naming should be consistent + # with BLAS library naming, so just prepend 'c' and try to detect it. + try: + cblas_pc = PkgConfigDependency('c'+name, env, kwargs) + if cblas_pc.found(): + self.link_args += cblas_pc.link_args + self.compile_args += cblas_pc.compile_args + except DependencyException: + pass + + if not self.probe_symbols(self.link_args): + self.is_found = False + + +class NetlibBLASSystemDependency(NetlibMixin, SystemDependency): + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs) -> None: + super().__init__(name, environment, kwargs) + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + + # First, look for paths specified in a machine file + props = self.env.properties[self.for_machine].properties + if any(x in props for x in ['blas_includedir', 'blas_librarydir']): + self.detect_blas_machine_file(props) + + # Then look in standard directories by attempting to link + if not self.is_found: + extra_libdirs: T.List[str] = [] + self.detect(extra_libdirs) + + if self.is_found: + self.version = 'unknown' # no way to derive this from standard headers + + def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[T.List[str]] = None) -> None: + if lib_dirs is None: + lib_dirs = [] + if inc_dirs is None: + inc_dirs = [] + + if self.interface == 'lp64': + libnames = ['blas'] + cblas_headers = ['cblas.h'] + elif self.interface == 'ilp64': + libnames = ['blas64', 'blas'] + cblas_headers = ['cblas_64.h', 'cblas.h'] + + for libname in libnames: + link_arg = self.clib_compiler.find_library(libname, lib_dirs) + if not link_arg: + continue + + # libblas may include CBLAS symbols (Debian builds it like this), + # but more often than not there's a separate libcblas library. Handle both cases. + if not self.probe_symbols(link_arg, check_cblas=False): + continue + if self.needs_cblas: + cblas_in_blas = self.probe_symbols(link_arg, check_cblas=True) + + if self.needs_cblas and not cblas_in_blas: + # We found libblas and it does not contain CBLAS symbols, so we need libcblas + cblas_libname = 'c' + libname + link_arg_cblas = self.clib_compiler.find_library(cblas_libname, lib_dirs) + if link_arg_cblas: + link_arg.extend(link_arg_cblas) + else: + # We didn't find CBLAS + continue + + self.is_found = True + self.link_args += link_arg + if self.needs_cblas: + incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs] + for hdr in cblas_headers: + found_header, _ = self.clib_compiler.has_header(hdr, '', dependencies=[self], + extra_args=incdir_args) + if found_header: + # If we don't get here, we found the library but not the header - this may + # be okay, since projects may ship their own CBLAS header for portability) + self.compile_args += incdir_args + break + + def detect_blas_machine_file(self, props: dict) -> None: + has_dirs, _dirs = check_blas_machine_file('blas', props) + if has_dirs: + libdir, incdir = _dirs + self.detect([libdir], [incdir]) + + +class NetlibLAPACKPkgConfigDependency(NetlibMixin, PkgConfigDependency): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs) -> None: + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + if self.interface == 'lp64' and '64' in name: + # *64* names are used for ILP64 variants + self.is_found = False + return None + + super().__init__(name, env, kwargs) + + if self.is_found: + if self.needs_lapacke: + # Similar to CBLAS: there may be a separate lapacke + # On Debian, it's lapack(64)?-netlib but lapacke(64)? + lapacke_name = name.replace('-netlib', '').replace('lapack', 'lapacke') + try: + lapacke_pc = PkgConfigDependency(lapacke_name, env, kwargs) + if lapacke_pc.found(): + self.link_args += lapacke_pc.link_args + self.compile_args += lapacke_pc.compile_args + except DependencyException: + pass + + if not self.probe_symbols(self.link_args, lapack_only=True): + self.is_found = False + + +class NetlibLAPACKSystemDependency(NetlibMixin, SystemDependency): + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs) -> None: + super().__init__(name, environment, kwargs) + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + + # First, look for paths specified in a machine file + props = self.env.properties[self.for_machine].properties + if any(x in props for x in ['lapack_includedir', 'lapack_librarydir']): + self.detect_lapack_machine_file(props) + + # Then look in standard directories by attempting to link + if not self.is_found: + extra_libdirs: T.List[str] = [] + self.detect(extra_libdirs) + + if self.is_found: + self.version = 'unknown' # no way to derive this from standard headers + + def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[T.List[str]] = None) -> None: + if lib_dirs is None: + lib_dirs = [] + if inc_dirs is None: + inc_dirs = [] + + if self.interface == 'lp64': + libnames = ['lapack'] + lapacke_headers = ['lapacke.h'] + elif self.interface == 'ilp64': + libnames = ['lapack64', 'lapack'] + lapacke_headers = ['lapacke_64.h', 'lapacke.h'] + + for libname in libnames: + link_arg = self.clib_compiler.find_library(libname, lib_dirs) + if not link_arg: + continue + + if not self.probe_symbols(link_arg, check_lapacke=False, lapack_only=True): + continue + if self.needs_lapacke: + lapacke_in_lapack = self.probe_symbols(link_arg, check_lapacke=True, lapack_only=True) + + if self.needs_lapacke and not lapacke_in_lapack: + # We found liblapack and it does not contain LAPACKE symbols, so we need liblapacke + lapacke_libname = libname + 'e' + link_arg_lapacke = self.clib_compiler.find_library(lapacke_libname, lib_dirs) + if link_arg_lapacke: + link_arg.extend(link_arg_lapacke) + else: + # We didn't find LAPACKE + continue + + self.is_found = True + self.link_args += link_arg + if self.needs_lapacke: + incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs] + for hdr in lapacke_headers: + found_header, _ = self.clib_compiler.has_header(hdr, '', dependencies=[self], + extra_args=incdir_args) + if found_header: + # If we don't get here, we found the library but not the header - this may + # be okay, since projects may ship their own LAPACKE header for portability) + self.compile_args += incdir_args + break + + def detect_lapack_machine_file(self, props: dict) -> None: + has_dirs, _dirs = check_blas_machine_file('lapack', props) + if has_dirs: + libdir, incdir = _dirs + self.detect([libdir], [incdir]) + + +class AccelerateSystemDependency(BLASLAPACKMixin, SystemDependency): + """ + Accelerate is always installed on macOS, and not available on other OSes. + We only support using Accelerate on macOS >=13.3, where Apple shipped a + major update to Accelerate, fixed a lot of bugs, and bumped the LAPACK + version from 3.2 to 3.9. The older Accelerate version is still available, + and can be obtained as a standard Framework dependency with one of: + + dependency('Accelerate') + dependency('appleframeworks', modules : 'Accelerate') + + """ + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs) -> None: + super().__init__(name, environment, kwargs) + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + + for_machine = MachineChoice.BUILD if kwargs.get('native', False) else MachineChoice.HOST + if environment.machines[for_machine].is_darwin() and self.check_macOS_recent_enough(): + self.detect(kwargs) + + def check_macOS_recent_enough(self) -> bool: + macos_version = platform.mac_ver()[0] + deploy_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', macos_version) + if not mesonlib.version_compare(deploy_target, '>=13.3'): + return False + + # We also need the SDK to be >=13.3 (meaning at least XCode 14.3) + cmd = ['xcrun', '-sdk', 'macosx', '--show-sdk-version'] + sdk_version = subprocess.run(cmd, capture_output=True, check=True, text=True, encoding='utf-8').stdout.strip() + return mesonlib.version_compare(sdk_version, '>=13.3') + + def detect(self, kwargs: DependencyObjectKWs) -> None: + from .framework import ExtraFrameworkDependency + dep = ExtraFrameworkDependency('Accelerate', self.env, kwargs) + self.is_found = dep.is_found + if self.is_found: + self.compile_args = dep.compile_args + self.link_args = dep.link_args + self.compile_args += ['-DACCELERATE_NEW_LAPACK'] + if self.interface == 'ilp64': + self.compile_args += ['-DACCELERATE_LAPACK_ILP64'] + + # We won't check symbols here, because Accelerate is built in a consistent fashion + # with known symbol mangling, unlike OpenBLAS or Netlib BLAS/LAPACK. + + def get_symbol_suffix(self) -> str: + return '$NEWLAPACK' if self.interface == 'lp64' else '$NEWLAPACK$ILP64' + + +class MKLMixin(BLASLAPACKMixin): + def get_symbol_suffix(self) -> str: + # _64 suffixes were added in 2022.2 + return '' if self.interface == 'lp64' else self._ilp64_suffix + + def parse_mkl_options(self, kwargs: DependencyObjectKWs) -> None: + """Parse `modules` and remove threading and SDL options from it if they are present. + + Removing 'threading: ' and 'sdl' from `modules` is needed to ensure those + don't get to the generic parse_modules() method for all BLAS/LAPACK dependencies. + """ + modules: T.List[str] = kwargs.get('modules', []) + threading_modules = [s for s in modules if s.startswith('threading: ')] + sdl_modules = [s for s in modules if s.startswith('sdl: ')] + + if not threading_modules: + self.threading = 'iomp' + elif len(threading_modules) > 1: + raise mesonlib.MesonException(f'Multiple threading arguments: {threading_modules}') + else: + # We have a single threading option specified - validate and process it + opt = threading_modules[0] + self.threading = opt.split(' ', 1)[1] + if self.threading not in ('seq', 'iomp', 'gomp', 'tbb'): + raise mesonlib.MesonException(f'Invalid threading argument: {opt}') + + modules = [s for s in modules if not s == opt] + kwargs['modules'] = modules + + if not sdl_modules: + self.use_sdl = 'auto' + elif len(sdl_modules) > 1: + raise mesonlib.MesonException(f'Multiple sdl arguments: {sdl_modules}') + else: + # We have a single sdl option specified - validate and process it + opt = sdl_modules[0] + self.use_sdl = opt.split(' ', 1)[1] + if self.use_sdl not in ('true', 'false', 'auto'): + raise mesonlib.MesonException(f'Invalid sdl argument: {opt}') + + modules = [s for s in modules if not s == opt] + kwargs['modules'] = modules + + # Parse common BLAS/LAPACK options + self.parse_modules(kwargs) + + # Check if we don't have conflicting options between SDL's defaults and interface/threading + self.sdl_default_opts = self.interface == 'lp64' and self.threading == 'iomp' + if self.use_sdl == 'auto' and not self.sdl_default_opts: + self.use_sdl = 'false' + elif self.use_sdl != 'false' and not self.sdl_default_opts: + # If we're here, we got an explicit `sdl: 'true'` + raise mesonlib.MesonException(f'Linking SDL implies using LP64 and Intel OpenMP, found ' + f'conflicting options: {self.interface}, {self.threading}') + + def probe_version(self, compile_args: list[str]) -> bool: + """Determine MKL version and ILP64 symbol suffix""" + ver, _ = self.clib_compiler.get_define('INTEL_MKL_VERSION', + '#include "mkl_version.h"', + dependencies=[self], + extra_args=compile_args) + if len(ver) == 8: + # mkl < 2025 uses (major * 10000 + minor * 100 + update) + # mkl >= 2025 uses (major * 10000 + update * 100 + patch) + # where releases are .. + # and seems to always be 0 + major = int(ver[:-4]) + if major >= 2025: + update = int(ver[-4:-2], 10) + patch = int(ver[-2:], 10) + else: + update = int(ver[-2:], 10) + patch = 0 + self.version = f'{major}.{update}.{patch}' + self._ilp64_suffix = '_64' if (major, update) >= (2022, 2) else '' + return True + return False + + +class MKLPkgConfigDependency(MKLMixin, PkgConfigDependency): + """ + pkg-config files for MKL were fixed recently, and should work from 2023.0 + onwards. Directly using a specific one like so should work: + + dependency('mkl-dynamic-ilp64-seq') + + The naming scheme is `mkl-libtype-interface-threading.pc`, with values: + - libtype: dynamic/static + - interface: lp64/ilp64 + - threading: seq/iomp/gomp/tbb + + Furthermore there is a pkg-config file for the Single Dynamic Library + (libmkl_rt.so) named `mkl-sdl.pc` (only added in 2023.0). + + Note that there is also an MKLPkgConfig dependency in scalapack.py, which + has more manual fixes. + """ + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs) -> None: + self.feature_since = ('1.11.0', '') + self.parse_mkl_options(kwargs) + if self.use_sdl == 'auto': + # Layered libraries are preferred, and .pc files for layered were + # available before the .pc file for SDL + self.use_sdl = 'false' + + static_opt = kwargs.get('static', env.coredata.optstore.get_value_for(OptionKey('prefer_static'))) + libtype = 'static' if static_opt else 'dynamic' + + if self.use_sdl == 'true': + name = 'mkl-sdl' + else: + name = f'mkl-{libtype}-{self.interface}-{self.threading}' + super().__init__(name, env, kwargs) + + if self.is_found and not self.probe_version(self.compile_args): + self.is_found = False + + +class MKLSystemDependency(MKLMixin, SystemDependency): + """This only detects MKL's Single Dynamic Library (SDL)""" + def __init__(self, name: str, environment: Environment, kwargs: DependencyObjectKWs) -> None: + super().__init__(name, environment, kwargs) + self.feature_since = ('1.11.0', '') + self.parse_mkl_options(kwargs) + + if self.use_sdl == 'auto': + # Too complex to use layered here - only supported with pkg-config + self.use_sdl = 'true' + + if self.use_sdl == 'true' and self.sdl_default_opts: + self.detect_sdl() + + def detect_sdl(self) -> None: + # Use MKLROOT in addition to standard libdir(s) + _m = os.environ.get('MKLROOT') + mklroot = Path(_m).resolve() if _m else None + lib_dirs = [] + inc_dirs = [] + if mklroot is not None: + libdir = mklroot / 'lib' / 'intel64' + if not libdir.exists(): + # MKLROOT may be pointing at the prefix where MKL was installed from PyPI + # or Conda (Intel supports those install methods, but dropped the `intel64` + # part, libraries go straight into /lib + libdir = mklroot / 'lib' + incdir = mklroot / 'include' + lib_dirs += [str(libdir)] + inc_dirs += [str(incdir)] + if not libdir.exists() or not incdir.exists(): + mlog.warning(f'MKLROOT env var set to {mklroot}, but not pointing to an MKL install') + + link_arg = self.clib_compiler.find_library('mkl_rt', lib_dirs) + if link_arg: + incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs] + found_header, _ = self.clib_compiler.has_header('mkl_version.h', '', + dependencies=[self], extra_args=incdir_args) + if link_arg and found_header: + self.is_found = True + self.compile_args += incdir_args + self.link_args += link_arg + if sys.platform != 'win32': + self.link_args += ['-lpthread', '-lm', '-ldl'] + + if not self.probe_version(self.compile_args): + self.is_found = False + + +class FlexiBLASPkgConfigDependency(BLASLAPACKMixin, PkgConfigDependency): + def __init__(self, name: str, env: Environment, kwargs: DependencyObjectKWs) -> None: + self.feature_since = ('1.11.0', '') + self.parse_modules(kwargs) + if self.interface == 'lp64' and '64' in name: + # *64* names are used for ILP64 variants + self.is_found = False + return None + + super().__init__(name, env, kwargs) + + if self.is_found: + if not self.probe_symbols(self.link_args): + self.is_found = False + + def get_symbol_suffix(self) -> str: + self._ilp64_suffix = '' # Handle `64_` suffix, or custom suffixes? + return '' if self.interface == 'lp64' else self._ilp64_suffix + + def probe_symbols(self, compile_args: list[str], check_cblas: bool = True, check_lapacke: bool = True, + lapack_only: bool = False) -> bool: + """FlexiBLAS 3.5.0 does not support suffixes yet, but it will follow Netlib LAPACK + in using the _64 suffix.""" + if self.interface == 'lp64': + return self.check_symbols(compile_args, check_cblas=check_cblas, + check_lapacke=check_lapacke, lapack_only=lapack_only) + + if self.check_symbols(compile_args, '_64', check_cblas=check_cblas, + check_lapacke=check_lapacke, lapack_only=lapack_only): + self._ilp64_suffix = '_64' + elif self.check_symbols(compile_args, '', check_cblas=check_cblas, + check_lapacke=check_lapacke, lapack_only=lapack_only): + self._ilp64_suffix = '' + else: + return False + return True + + +@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE}) +def openblas_factory(env: Environment, + kwargs: DependencyObjectKWs, + methods: T.List[DependencyMethods]) -> T.List[DependencyGenerator]: + candidates: T.List[DependencyGenerator] = [] + + if DependencyMethods.PKGCONFIG in methods: + for pkg in ['openblas64', 'openblas_ilp64', 'openblas']: + candidates.append(DependencyCandidate.from_dependency( + pkg, OpenBLASPkgConfigDependency, (env, kwargs))) + + if DependencyMethods.SYSTEM in methods: + candidates.append(DependencyCandidate.from_dependency( + 'openblas', OpenBLASSystemDependency, (env, kwargs))) + + if DependencyMethods.CMAKE in methods: + candidates.append(DependencyCandidate.from_dependency( + 'OpenBLAS', OpenBLASCMakeDependency, (env, kwargs))) + + return candidates + +packages['openblas'] = openblas_factory + + +@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}) +def netlib_blas_factory(env: Environment, + kwargs: DependencyObjectKWs, + methods: T.List[DependencyMethods]) -> T.List[DependencyGenerator]: + candidates: T.List[DependencyGenerator] = [] + + if DependencyMethods.PKGCONFIG in methods: + for pkg in ['blas64-netlib', 'blas-netlib', 'blas64', 'blas']: + candidates.append(DependencyCandidate.from_dependency( + pkg, NetlibBLASPkgConfigDependency, (env, kwargs))) + + if DependencyMethods.SYSTEM in methods: + candidates.append(DependencyCandidate.from_dependency( + 'blas', NetlibBLASSystemDependency, (env, kwargs))) + + return candidates + + +@factory_methods({DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM}) +def netlib_lapack_factory(env: Environment, + kwargs: DependencyObjectKWs, + methods: T.List[DependencyMethods]) -> T.List[DependencyGenerator]: + candidates: T.List[DependencyGenerator] = [] + + if DependencyMethods.PKGCONFIG in methods: + for pkg in ['lapack64-netlib', 'lapack-netlib', 'lapack64', 'lapack']: + candidates.append(DependencyCandidate.from_dependency( + pkg, NetlibLAPACKPkgConfigDependency, (env, kwargs))) + + if DependencyMethods.SYSTEM in methods: + candidates.append(DependencyCandidate.from_dependency( + 'lapack', NetlibLAPACKSystemDependency, (env, kwargs))) + + return candidates + + +@factory_methods({DependencyMethods.PKGCONFIG}) +def flexiblas_factory(env: Environment, + kwargs: DependencyObjectKWs, + methods: T.List[DependencyMethods]) -> T.List[DependencyGenerator]: + candidates: T.List[DependencyGenerator] = [] + + if DependencyMethods.PKGCONFIG in methods: + for pkg in ['flexiblas64', 'flexiblas']: + candidates.append(DependencyCandidate.from_dependency( + pkg, FlexiBLASPkgConfigDependency, (env, kwargs))) + + return candidates + + +packages['openblas'] = openblas_factory +packages['blas'] = netlib_blas_factory +packages['lapack'] = netlib_lapack_factory +packages['flexiblas'] = flexiblas_factory + +packages['accelerate'] = DependencyFactory( + 'accelerate', + methods=[DependencyMethods.SYSTEM], + system=AccelerateSystemDependency, +) + +packages['mkl'] = DependencyFactory( + 'mkl', + methods=[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM], + pkgconfig=MKLPkgConfigDependency, + system=MKLSystemDependency, +) diff --git a/test cases/frameworks/41 blas-lapack/cblas_lapack.c b/test cases/frameworks/41 blas-lapack/cblas_lapack.c new file mode 100644 index 000000000000..3ebdccb7a43f --- /dev/null +++ b/test cases/frameworks/41 blas-lapack/cblas_lapack.c @@ -0,0 +1,47 @@ +// Basic BLAS/LAPACK example adapted from a test in Spack for OpenBLAS + +#include "cblas_lapack.h" + +int main(void) { + // CBLAS: + blas_int n_elem = 9; + blas_int incx = 1; + double A[6] = {1.0, 2.0, 1.0, -3.0, 4.0, -1.0}; + double B[6] = {1.0, 2.0, 1.0, -3.0, 4.0, -1.0}; + double C[9] = {.5, .5, .5, .5, .5, .5, .5, .5, .5}; + double norm; + + CBLAS_FUNC(cblas_dgemm) + (CblasColMajor, CblasNoTrans, CblasTrans, 3, 3, 2, 1, A, 3, B, 3, 2, C, 3); + norm = CBLAS_FUNC(cblas_dnrm2)(n_elem, C, incx) - 28.017851; + + if (fabs(norm) < 1e-5) { + printf("OK: CBLAS result using dgemm and dnrm2 as expected\n"); + } else { + fprintf(stderr, "CBLAS result using dgemm and dnrm2 incorrect: %f\n", norm); + exit(EXIT_FAILURE); + } + + // LAPACK: + double m[] = {3, 1, 3, 1, 5, 9, 2, 6, 5}; + double x[] = {-1, 3, -3}; + lapack_int ipiv[3]; + lapack_int info; + lapack_int n = 1; + lapack_int nrhs = 1; + lapack_int lda = 3; + lapack_int ldb = 3; + + LAPACK_FUNC(dgesv)(&n, &nrhs, &m[0], &lda, ipiv, &x[0], &ldb, &info); + n_elem = 3; + norm = CBLAS_FUNC(cblas_dnrm2)(n_elem, x, incx) - 4.255715; + + if (fabs(norm) < 1e-5) { + printf("OK: LAPACK result using dgesv as expected\n"); + } else { + fprintf(stderr, "LAPACK result using dgesv incorrect: %f\n", norm); + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/test cases/frameworks/41 blas-lapack/cblas_lapack.h b/test cases/frameworks/41 blas-lapack/cblas_lapack.h new file mode 100644 index 000000000000..5b8ddc023d9d --- /dev/null +++ b/test cases/frameworks/41 blas-lapack/cblas_lapack.h @@ -0,0 +1,113 @@ +// Unified CBLAS and LAPACK header to account for the various name mangling +// schemes used by Accelerate, OpenBLAS, MKL & co. As well as for the names of +// header files varying (e.g. cblas.h, Accelerate/Accelerate.h, mkl_cblas.h) +// +// Accelerate.h (via cblas_new.h) contains non-suffixed names, and the suffixed +// symbols are added via aliases inserted with __asm. +// OpenBLAS headers do contain suffixed names (e.g., `cblas_dgemm64_`) +// The end result after the standardization discussion in +// https://github.com/Reference-LAPACK/lapack/issues/666 should be +// `cblas_dgemm_64`, however that isn't yet final or implemented. +// +// # Actual symbols present in MKL: + +// 00000000003e4970 T cblas_dgemm +// 00000000003e4970 T cblas_dgemm_ +// 00000000003e34e0 T cblas_dgemm_64 +// 00000000003e34e0 T cblas_dgemm_64_ +// +// 00000000004e9f80 T dgesv +// 00000000004e9f80 T dgesv_ +// 00000000004ea050 T dgesv_64 +// 00000000004ea050 T dgesv_64_ +// +// +// # Actual symbols present in OpenBLAS (in `libopenblas64`): +// +// 00000000000a3430 T cblas_dgemm64_ +// +// 00000000000a6e50 T dgesv_64_ + +// Name mangling adapted/extended from NumPy's npy_cblas.h +#include +#include +#include + +#ifdef ACCELERATE_NEW_LAPACK +#if __MAC_OS_X_VERSION_MAX_ALLOWED < 130300 +#ifdef HAVE_BLAS_ILP64 +#error "Accelerate ILP64 support is only available with macOS 13.3 SDK or later" +#endif +#else +#define NO_APPEND_FORTRAN +#ifdef HAVE_BLAS_ILP64 +#define BLAS_SYMBOL_SUFFIX $NEWLAPACK$ILP64 +#else +#define BLAS_SYMBOL_SUFFIX $NEWLAPACK +#endif +#endif +#endif + +#ifdef NO_APPEND_FORTRAN +#define BLAS_FORTRAN_SUFFIX +#else +#define BLAS_FORTRAN_SUFFIX _ +#endif + +#ifndef BLAS_SYMBOL_SUFFIX +#define BLAS_SYMBOL_SUFFIX +#endif + +#define BLAS_FUNC_CONCAT(name, suffix, suffix2) name##suffix##suffix2 +#define BLAS_FUNC_EXPAND(name, suffix, suffix2) \ + BLAS_FUNC_CONCAT(name, suffix, suffix2) + +#define CBLAS_FUNC(name) BLAS_FUNC_EXPAND(name, , BLAS_SYMBOL_SUFFIX) +#ifdef OPENBLAS_ILP64_NAMING_SCHEME +#define BLAS_FUNC(name) \ + BLAS_FUNC_EXPAND(name, BLAS_FORTRAN_SUFFIX, BLAS_SYMBOL_SUFFIX) +#define LAPACK_FUNC(name) BLAS_FUNC(name) +#else +#define BLAS_FUNC(name) \ + BLAS_FUNC_EXPAND(name, BLAS_SYMBOL_SUFFIX, BLAS_FORTRAN_SUFFIX) +#define LAPACK_FUNC(name) BLAS_FUNC(name) +#endif + +#ifdef HAVE_BLAS_ILP64 +#define blas_int long +#define lapack_int long +#else +#define blas_int int +#define lapack_int int +#endif + +enum CBLAS_ORDER { CblasRowMajor = 101, CblasColMajor = 102 }; +enum CBLAS_TRANSPOSE { + CblasNoTrans = 111, + CblasTrans = 112, + CblasConjTrans = 113 +}; + +#ifdef __cplusplus +extern "C" { +#endif + +void CBLAS_FUNC(cblas_dgemm)(const enum CBLAS_ORDER Order, + const enum CBLAS_TRANSPOSE TransA, + const enum CBLAS_TRANSPOSE TransB, + const blas_int M, const blas_int N, + const blas_int K, const double alpha, + const double *A, const blas_int lda, + const double *B, const blas_int ldb, + const double beta, double *C, const blas_int ldc); + +double CBLAS_FUNC(cblas_dnrm2)(const blas_int N, const double *X, + const blas_int incX); + +void LAPACK_FUNC(dgesv)(lapack_int *n, lapack_int *nrhs, double *a, + lapack_int *lda, lapack_int *ipivot, double *b, + lapack_int *ldb, lapack_int *info); + +#ifdef __cplusplus +} +#endif diff --git a/test cases/frameworks/41 blas-lapack/main.f90 b/test cases/frameworks/41 blas-lapack/main.f90 new file mode 100644 index 000000000000..9abfc0676ef2 --- /dev/null +++ b/test cases/frameworks/41 blas-lapack/main.f90 @@ -0,0 +1,25 @@ +! minimal BLAS test +program AHH + +implicit none +integer :: n, incx, xs +real :: multval +integer :: x(4) +external sccal + +! A very simple test case, scalar x vector +n = 4 +multval = 3.0 +incx = 1 +x = [1, 2, 3, 4] + +call sscal(n, multval, x, incx) + +xs = int(sum(x)) + +if (xs == 30) then + print '("OK: BLAS sum of scaled 1-D array = ",i2)', xs +else + print '("NOK: BLAS sum of scaled 1-D array = ",i2)', xs +end if +end diff --git a/test cases/frameworks/41 blas-lapack/meson.build b/test cases/frameworks/41 blas-lapack/meson.build new file mode 100644 index 000000000000..f894188450fd --- /dev/null +++ b/test cases/frameworks/41 blas-lapack/meson.build @@ -0,0 +1,110 @@ +project('test blas and lapack', 'c') + +has_fortran = add_languages('fortran', required: false) + +found_any = false +foreach name : ['openblas', 'accelerate', 'flexiblas', 'mkl', 'netlib'] + foreach interface : ['lp64', 'ilp64'] + # we treat 'sdl' here since it implies lp64+iomp, so we can avoid one + # level of nesting + foreach threading : ['seq', 'iomp', 'gomp', 'tbb', 'sdl'] + modules = [f'interface: @interface@'] + + # threading and sdl are only relevant to mkl + if name == 'mkl' + if threading == 'sdl' + # sdl is valid only for lp64 + if interface != 'lp64' + continue + endif + modules += ['sdl: true'] + else + modules += [f'threading: @threading@'] + endif + elif threading != 'seq' + continue + endif + + # for netlib, we need to query cblas & lapack separately + if name == 'netlib' + blas = dependency('blas', + modules: modules + ['cblas'], + required: false, + ) + lapack = dependency('lapack', + modules: modules + ['lapack', 'lapacke'], + required: false, + ) + if not blas.found() or not lapack.found() + continue + endif + foreach var : ['interface', 'symbol_suffix'] + assert(blas.get_variable(var) == lapack.get_variable(var)) + endforeach + deps = [blas, lapack] + else + dep = dependency(name, + modules: modules + ['cblas', 'lapack', 'lapacke'], + required: false, + ) + if not dep.found() + continue + endif + deps = [dep] + endif + + found_any = true + blas_interface = deps[0].get_variable('interface') + symbol_suffix = deps[0].get_variable('symbol_suffix') + found_names = [] + foreach dep : deps + found_names += [dep.name()] + endforeach + message(f'Found @name@ @blas_interface@ @threading@: @found_names@; symbol suffix: @symbol_suffix@') + assert(blas_interface == interface) + + blas_c_args = ['-DBLAS_SYMBOL_SUFFIX=' + symbol_suffix] + if blas_interface == 'ilp64' + blas_c_args += ['-DHAVE_BLAS_ILP64'] + if 'openblas' in dep.name() + blas_c_args += ['-DOPENBLAS_ILP64_NAMING_SCHEME'] + endif + elif blas_interface != 'lp64' + error(f'no interface var for @name@ dependency!') + endif + + exe_suffix = f'@name@_@interface@_@threading@' + c_exe = executable(f'cblas_lapack_@exe_suffix@_c', + 'cblas_lapack.c', + c_args: blas_c_args, + dependencies: deps + ) + test(f'cblas_lapack_dep_@exe_suffix@_c', c_exe, timeout: 30) + + # TODO: mkl-ilp64 fortran executable segfaults frequently + # netlib fortran executables too + if has_fortran + fc_id = meson.get_compiler('fortran').get_id() + fc_args = [] + if fc_id == 'gcc' + # TODO: why is netlib giving us ilp64? + if interface == 'ilp64' or name == 'netlib' + fc_args += ['-fdefault-integer-8'] + endif + else + error(f'Unknown Fortran compiler @fc_id@') + endif + f_exe = executable(f'cblas_lapack_@exe_suffix@_fortran', + 'main.f90', + dependencies: deps, + fortran_args: fc_args, + ) + test(f'cblas_lapack_dep_@exe_suffix@_fortran', f_exe, timeout: 30) + endif + endforeach + endforeach +endforeach + +if not found_any + error('MESON_SKIP_TEST: no BLAS/LAPACK libraries available') +endif diff --git a/test cases/frameworks/41 blas-lapack/test.json b/test cases/frameworks/41 blas-lapack/test.json new file mode 100644 index 000000000000..f935d59ffe99 --- /dev/null +++ b/test cases/frameworks/41 blas-lapack/test.json @@ -0,0 +1,3 @@ +{ + "expect_skip_on_jobname": ["azure", "bionic", "cygwin", "msys2"] +}