Skip to content

Commit

Permalink
Add support for LP64/ILP64 interface via modules keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
rgommers committed Jan 7, 2023
1 parent a46949f commit 2e00831
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 34 deletions.
91 changes: 71 additions & 20 deletions mesonbuild/dependencies/blas_lapack.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
from .. import mesonlib

from .base import DependencyMethods, SystemDependency
from .cmake import CMakeDependency
from .factory import DependencyFactory
from .pkgconfig import PkgConfigDependency

if T.TYPE_CHECKING:
from ..environment import Environment
Expand Down Expand Up @@ -162,7 +164,7 @@
mkl-dynamic-ilp64-seq.pc mkl-dynamic-lp64-seq.pc mkl-static-ilp64-seq.pc mkl-static-lp64-seq.pc
$ pkg-config --libs mkl-dynamic-ilp64-seq
-L/opt/intel/oneapi/mkl/latest/lib/pkgconfig/../../lib/intel64 -lmkl_intel_ilp64 -lmkl_sequential -lmkl_core -lpthread -lm -ldl
-L/opt/intel/oneapi/mkl/latest/lib/pkgconfig/../../lib/intel64 -lmkl_intel_ilp64 -lmkl_sequential -lmkl_core -lpthread -lm -ldl
$ pkg-config --cflags mkl-dynamic-ilp64-seq
-DMKL_ILP64 -I/opt/intel/oneapi/mkl/latest/lib/pkgconfig/../../include
Expand Down Expand Up @@ -231,11 +233,37 @@
"""


class OpenBLASSystemDependency(SystemDependency):
class OpenBLASMixin():
def parse_modules(self, kwargs: T.Dict[str, T.Any]) -> None:
modules: T.List[str] = mesonlib.extract_as_list(kwargs, 'modules')
valid_modules = ['interface: lp64', 'interface: ilp64']
for module in modules:
if module not in valid_modules:
raise mesonlib.MesonException(f'Unknown modules argument: {module}')

self.interface = ''
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]

def get_variable(self, **kwargs: T.Dict[str, T.Any]) -> str:
# TODO: what's going on with `get_variable`? Need to pick from
# cmake/pkgconfig/internal/..., but not system?
varname = kwargs['pkgconfig']
if varname == 'interface':
return self.interface
return super().get_variable(kwargs) # FIXME: not quite allowed by Mypy


class OpenBLASSystemDependency(OpenBLASMixin, SystemDependency):
def __init__(self, name: str, environment: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
super().__init__(name, environment, kwargs)
self.feature_since = ('0.64.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']):
Expand All @@ -258,23 +286,30 @@ def detect(self, lib_dirs: T.Optional[T.List[str]] = None, inc_dirs: T.Optional[
if inc_dirs is None:
inc_dirs = []

link_arg = self.clib_compiler.find_library('openblas', self.env, lib_dirs)
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
found_header, _ = self.clib_compiler.has_header('openblas_config.h', '', self.env,
dependencies=[self], extra_args=incdir_args)
if link_arg and found_header:
self.is_found = True
if lib_dirs:
# `link_arg` will be either `[-lopenblas]` or `[/path_to_sharedlib/libopenblas.so]`
# is the latter behavior expected?
found_libdir = Path(link_arg[0]).parent
self.link_args += [f'-L{found_libdir}', '-lopenblas']
else:
self.link_args += link_arg

# has_header does not return a path with where the header was
# found, so add all provided include directories
self.compile_args += incdir_args
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, self.env, lib_dirs)
incdir_args = [f'-I{inc_dir}' for inc_dir in inc_dirs]
found_header, _ = self.clib_compiler.has_header('openblas_config.h', '', self.env,
dependencies=[self], extra_args=incdir_args)
if link_arg and found_header:
self.is_found = True
if lib_dirs:
# `link_arg` will be either `[-lopenblas]` or `[/path_to_sharedlib/libopenblas.so]`
# is the latter behavior expected?
found_libdir = Path(link_arg[0]).parent
self.link_args += [f'-L{found_libdir}', f'-l{libname}']
else:
self.link_args += link_arg

# has_header does not return a path with where the header was
# found, so add all provided include directories
self.compile_args += incdir_args
return None

def detect_openblas_machine_file(self, props: dict) -> None:
# TBD: do we need to support multiple extra dirs?
Expand Down Expand Up @@ -307,6 +342,7 @@ def detect_openblas_version(self) -> str:
return m.group(0)

def run_check(self) -> None:
# TODO! verify that we've found the right LP64/ILP64 interface
# See https://github.com/numpy/numpy/blob/main/numpy/distutils/system_info.py#L2319
# Symbols to check:
# for BLAS LP64: dgemm # note that numpy.distutils checks nothing here
Expand All @@ -316,9 +352,24 @@ def run_check(self) -> None:
pass


class OpenBLASPkgConfigDependency(OpenBLASMixin, PkgConfigDependency):
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any]) -> None:
self.parse_modules(kwargs)
super().__init__(name, env, kwargs)


class OpenBLASCMakeDependency(OpenBLASMixin, CMakeDependency):
def __init__(self, name: str, env: 'Environment', kwargs: T.Dict[str, T.Any],
language: T.Optional[str] = None, force_use_global_compilers: bool = False) -> None:
self.parse_modules(kwargs)
# TODO: support ILP64
super().__init__('OpenBLAS', env, kwargs, language, force_use_global_compilers)


openblas_factory = DependencyFactory(
'openblas',
[DependencyMethods.PKGCONFIG, DependencyMethods.SYSTEM, DependencyMethods.CMAKE],
cmake_name='OpenBLAS',
system_class=OpenBLASSystemDependency,
pkgconfig_class=OpenBLASPkgConfigDependency,
cmake_class=OpenBLASCMakeDependency,
)
40 changes: 31 additions & 9 deletions test cases/frameworks/36 blas lapack/cblas_lapack.c
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
// Adapted from a test in Spack for OpenBLAS
// Basic BLAS/LAPACK example adapted from a test in Spack for OpenBLAS
// Name mangling adapted from NumPy's npy_cblas.h

#include <cblas.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef NO_APPEND_FORTRAN
#define BLAS_FORTRAN_SUFFIX
#else
#define BLAS_FORTRAN_SUFFIX _
#endif

#ifndef BLAS_SYMBOL_PREFIX
#define BLAS_SYMBOL_PREFIX
#endif

#ifndef BLAS_SYMBOL_SUFFIX
#define BLAS_SYMBOL_SUFFIX
#endif

#define BLAS_FUNC_CONCAT(name,prefix,suffix,suffix2) prefix ## name ## suffix ## suffix2
#define BLAS_FUNC_EXPAND(name,prefix,suffix,suffix2) BLAS_FUNC_CONCAT(name,prefix,suffix,suffix2)

#define CBLAS_FUNC(name) BLAS_FUNC_EXPAND(name,BLAS_SYMBOL_PREFIX,,BLAS_SYMBOL_SUFFIX)
#define BLAS_FUNC(name) BLAS_FUNC_EXPAND(name,BLAS_SYMBOL_PREFIX,BLAS_FORTRAN_SUFFIX,BLAS_SYMBOL_SUFFIX)


#ifdef __cplusplus
extern "C" {
#endif

void dgesv_(int *n, int *nrhs, double *a, int *lda, int *ipivot, double *b,
int *ldb, int *info);
void BLAS_FUNC(dgesv)(int *n, int *nrhs, double *a, int *lda, int *ipivot, double *b,
int *ldb, int *info);

#ifdef __cplusplus
}
Expand All @@ -25,9 +47,9 @@ int main(void) {
int n_elem = 9;
double norm;

cblas_dgemm(CblasColMajor, CblasNoTrans, CblasTrans, 3, 3, 2, 1, A, 3, B, 3,
CBLAS_FUNC(cblas_dgemm)(CblasColMajor, CblasNoTrans, CblasTrans, 3, 3, 2, 1, A, 3, B, 3,
2, C, 3);
norm = cblas_dnrm2(n_elem, C, incx) - 28.017851;
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");
Expand All @@ -46,14 +68,14 @@ int main(void) {
int lda = 3;
int ldb = 3;

dgesv_(&n, &nrhs, &m[0], &lda, ipiv, &x[0], &ldb, &info);
BLAS_FUNC(dgesv)(&n, &nrhs, &m[0], &lda, ipiv, &x[0], &ldb, &info);
n_elem = 3;
norm = cblas_dnrm2(n_elem, x, incx) - 4.255715;
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");
printf("OK: LAPACK result using dgesv as expected\n");
} else {
fprintf(stderr, "LAPACK result using dgesv_ incorrect: %f\n", norm);
fprintf(stderr, "LAPACK result using dgesv incorrect: %f\n", norm);
exit(EXIT_FAILURE);
}

Expand Down
26 changes: 21 additions & 5 deletions test cases/frameworks/36 blas lapack/meson.build
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
project('test blas and lapack', 'c')

openblas_dep = dependency('openblas', required: false)
# NOTE: this is for local testing, we probably only want a LP64 openblas
# in the Meson CI. We need to check symbols though! We may now pick up a
# wrong binary
openblas_dep = dependency('openblas', required: false, modules: ['interface: ilp64'])
if not openblas_dep.found()
error('MESON_SKIP_TEST: OpenBLAS library not available')
endif

c_exe = executable('cblas_lapack_c', 'cblas_lapack.c',
dependencies: [openblas_dep])
blas_c_args = []
blas_interface = openblas_dep.get_variable('interface')
if blas_interface == 'ilp64'
blas_c_args += '-DBLAS_SYMBOL_SUFFIX=64_'
elif blas_interface != 'lp64'
error('no interface var for OpenBLAS dependency!')
endif

c_exe = executable('cblas_lapack_c',
'cblas_lapack.c',
c_args: blas_c_args,
dependencies: [openblas_dep]
)
test('openblas_dep', c_exe, timeout: 30)

if add_languages('fortran', required: false)
f_exe = executable('openblas_fortran', 'main.f90',
dependencies: [openblas_dep])
f_exe = executable('openblas_fortran',
'main.f90',
dependencies: [openblas_dep]
)
test('openblas_fortran', f_exe, timeout: 30)
endif

0 comments on commit 2e00831

Please sign in to comment.