I'm developing a small package that wraps OpenBLAS (and LAPACKE therein) for my linear algebra library, since there doesn't seem to be such a package available that works for both Windows and Linux (on macOS I use Accelerate). After pondering for a while on how to make this package most distributable (for my fellow researchers), I decided to load the OpenBLAS symbols at runtime, using the following approach:
#if os(Windows)
import WinSDK
#elseif os(Linux)
import Linux
#endif
enum OpenBLAS {
#if os(Windows)
static let handle: HMODULE? = {
if let handle = LoadLibraryA("libopenblas.dll") {
return handle
}
print("Failed to load libopenblas.dll")
return nil
}()
#elseif os(Linux)
static let handle: UnsafeMutableRawPointer? = {
if let handle = dlopen("libopenblas.so", RTLD_NOW | RTLD_LOCAL) {
return handle
}
print("Failed to load libopenblas.so")
return nil
}()
#endif
static func load<T>(_ name: String, as type: T.Type = T.self) -> T? {
guard let handle = handle else { return nil }
#if os(Windows)
let symbol = GetProcAddress(handle, name)
#elseif os(Linux)
let symbol = dlsym(handle, name)
#endif
if let symbol = symbol { return unsafeBitCast(symbol, to: type) }
return nil
}
static let dscal: CBLAS_DSCAL = load("cblas_dscal")
...
}
typealias CBLAS_DSCAL = @convention(c) (_ N: Int32, _ alpha: Double, _ X: UnsafeMutablePointer<Double>?, _ incX: Int32) -> Void
...
Design note: The symbols are returned as optionals to support the case where OpenBLAS is not present on the user's system. In such cases, we fall back to naive implementations of the BLAS/LAPACK functions, etc.
So far, this has been a successful—albeit somewhat cumbersome—approach. The "problem" arises when I want to interface with LAPACKE functions that take complex inputs. The CBLAS functions accept complex values as Unsafe*RawPointer
, which is very straightforward to interface with. However, LAPACKE expects complex inputs as pointers to structs like lapack_complex_double
which are defined differently depending on the platform.
For example, the function LAPACKE_dgesv
has the following signature:
lapack_int LAPACKE_dgesv(int matrix_layout, lapack_int n, lapack_int nrhs,
lapack_complex_double* a, lapack_int lda,
lapack_int* ipiv, lapack_complex_double* b,
lapack_int ldb );
On Windows, it's imported as:
func LAPACKE_dgesv(_ matrix_layout: Int32, _ n: Int32, _ nrhs: Int32,
_ a: UnsafeMutablePointer<_Dcomplex>?, _ lda: Int32,
_ ipiv: UnsafeMutablePointer<Int32>?, _ b: UnsafeMutablePointer<_Dcomplex>?,
_ ldb: Int32) -> Int32
Here, the type _Dcomplex
is essentially just a pair of Double
values.
So, my question is: can I "safely" load, for example, LAPACKE_dgesv
using the following signature?
typealias LAPACKE_DGESV = @convention(c) (_ matrix_layout: Int32, _ n: Int32, _ nrhs: Int32,
_ a: UnsafeMutableRawPointer?, _ lda: Int32,
_ ipiv: UnsafeMutablePointer<Int32>?, _ b: UnsafeMutableRawPointer?,
_ ldb: Int32) -> Int32
That is, can I safely switch from UnsafeMutablePointer<_Dcomplex>
to UnsafeMutableRawPointer
or OpaquePointer
when loading these symbols?